smart-components
Smart State ✦

Form autofill

Type a one-line brief → fill a strongly-typed event form with one call.

Seed the form with empty fields of the right types, pass a plain-English brief, and ai.generate(brief) fills every field. The hook validates the response against the seeded shape — missing fields surface as ai.error.

Skeleton

The raw plumbing — same hook, no styling.

Title
Attendees
When
Where
Notes
const [brief, setBrief] = useState('lunch with Sarah tomorrow at noon at Olive Garden');const [event, , ai] = useSmartState({ title: '', attendees: [''], datetimeISO: '', location: '', notes: '' },'A calendar event extracted from a one-line brief',);<textarea value={brief} onChange={(e) => setBrief(e.target.value)} /><button onClick={() => ai.generate(brief)}>Fill form</button><dl><dt>Title</dt><dd>{event.title || '—'}</dd><dt>Attendees</dt><dd>{event.attendees.filter(Boolean).join(', ') || '—'}</dd><dt>When</dt><dd>{event.datetimeISO || '—'}</dd><dt>Where</dt><dd>{event.location || '—'}</dd><dt>Notes</dt><dd>{event.notes || '—'}</dd></dl>

Stylized

Quick-pick preset chips, a formatted date, attendee pills, and skeleton-loading animation.

Title
When
Where
Attendees
Notes
'use client';import { useState } from 'react';import { SmartProvider, useSmartState } from '@extedcoud/smart-components';import { makeSmartStateMock } from '@/lib/mock-client';const client = makeSmartStateMock(500);const PRESETS = [  'lunch with Sarah tomorrow at noon at Olive Garden',  'project kickoff sync next Monday 10am on Zoom',  'onsite interview with senior engineer candidate',];function formatWhen(iso: string) {  if (!iso) return '';  try {    const d = new Date(iso);    return d.toLocaleString('en-US', {      weekday: 'short',      month: 'short',      day: 'numeric',      hour: 'numeric',      minute: '2-digit',    });  } catch {    return iso;  }}function Demo() {  const [brief, setBrief] = useState(PRESETS[0]);  const [event, , ai] = useSmartState(    { title: '', attendees: [''], datetimeISO: '', location: '', notes: '' },    'A calendar event extracted from a one-line brief',  );  const loading = ai.status === 'loading';  const filled = !!event.title;  return (    <div className="w-full max-w-xl">      <div className="rounded-2xl border border-fd-border bg-fd-card p-5 shadow-sm">        <label          htmlFor="smart-state-brief"          className="block text-xs font-medium uppercase tracking-wider text-fd-muted-foreground"        >          One-line brief        </label>        <textarea          id="smart-state-brief"          value={brief}          onChange={(e) => setBrief(e.target.value)}          rows={2}          style={{ fontSize: 16 }}          className="mt-2 w-full resize-none rounded-lg border border-fd-border bg-fd-background px-3 py-2 text-base text-fd-foreground outline-none transition focus:border-fd-foreground"        />        <div className="mt-2 flex flex-wrap gap-2">          {PRESETS.map((p) => (            <button              key={p}              onClick={() => setBrief(p)}              className="inline-flex items-center rounded-full border border-fd-border bg-fd-background px-3 py-1 text-[11px] text-fd-muted-foreground transition hover:bg-fd-accent"              style={{ touchAction: 'manipulation' }}            >              {p.length > 40 ? p.slice(0, 40) + '…' : p}            </button>          ))}        </div>        <button          onClick={() => ai.generate(brief)}          disabled={loading || !brief.trim()}          className="mt-3 inline-flex h-10 min-h-[44px] w-full items-center justify-center gap-2 rounded-lg bg-gradient-to-r from-indigo-500 to-cyan-500 text-sm font-semibold text-white shadow transition hover:opacity-90 disabled:opacity-50"          style={{ touchAction: 'manipulation' }}        >          {loading ? (            <>              <span className="h-3 w-3 animate-spin rounded-full border-2 border-white/40 border-t-white" />              Filling…            </>          ) : (            <>✦ Fill the form</>          )}        </button>        <div className="mt-5 rounded-xl border border-fd-border bg-fd-background">          <div className="grid grid-cols-[88px_1fr] gap-y-3 px-4 py-4 text-sm">            <div className="text-xs uppercase tracking-wider text-fd-muted-foreground">Title</div>            <div className={`font-medium text-fd-foreground ${loading ? 'animate-pulse' : ''}`}>              {filled ? event.title : <span className="text-fd-muted-foreground">—</span>}            </div>            <div className="text-xs uppercase tracking-wider text-fd-muted-foreground">When</div>            <div className={`text-fd-foreground ${loading ? 'animate-pulse' : ''}`}>              {filled ? formatWhen(event.datetimeISO) : <span className="text-fd-muted-foreground">—</span>}            </div>            <div className="text-xs uppercase tracking-wider text-fd-muted-foreground">Where</div>            <div className={`text-fd-foreground ${loading ? 'animate-pulse' : ''}`}>              {filled ? event.location : <span className="text-fd-muted-foreground">—</span>}            </div>            <div className="text-xs uppercase tracking-wider text-fd-muted-foreground">Attendees</div>            <div className={`flex flex-wrap gap-1 ${loading ? 'animate-pulse' : ''}`}>              {filled ? (                event.attendees.filter(Boolean).map((a, i) => (                  <span                    key={`${a}-${i}`}                    className="inline-flex items-center rounded-full bg-indigo-500/10 px-2 py-0.5 text-xs font-medium text-indigo-700 dark:text-indigo-300"                  >                    {a}                  </span>                ))              ) : (                <span className="text-fd-muted-foreground">—</span>              )}            </div>            <div className="text-xs uppercase tracking-wider text-fd-muted-foreground">Notes</div>            <div className={`text-sm text-fd-muted-foreground ${loading ? 'animate-pulse' : ''}`}>              {filled ? event.notes : '—'}            </div>          </div>        </div>        {ai.error && <p className="mt-3 text-xs text-red-500">{ai.error.message}</p>}      </div>    </div>  );}export default function SmartStateFormStylized() {  return (    <SmartProvider client={client}>      <Demo />    </SmartProvider>  );}

Why this works

The shape { title: string, attendees: string[], datetimeISO: string, location: string, notes: string } is enough for the model to know exactly what to return. Behind the scenes:

  1. The seeded value is introspected into a ShapeDescriptor.
  2. A system prompt is generated asking for JSON only, matching that schema.
  3. The response is JSON.parsed and validated against the shape — missing/wrong-type fields become ai.error.

Production swap

The skeleton and stylized demos use a mock client for predictable output. In production, swap the provider:

import { SmartProvider } from '@extedcoud/smart-components';
import { createOpenAIClient } from '@extedcoud/smart-components/adapters/openai';

const client = createOpenAIClient({ /* via your proxy */ });

<SmartProvider client={client}>
  <YourFormComponent />
</SmartProvider>

The hook code doesn't change.