Shadcn RegistryAI Elements
Suggestions
GencnUI-powered suggestion component helps you create variants of a sentence based on the provided context.
ai-elements suggestion component is powered by the use-gencn-ui-suggestions hook.
Loading preview...
"use client"; import * as React from "react"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Spinner } from "@/components/ui/spinner"; import { Suggestions, Suggestion } from "@/components/ai-elements/suggestion"; import { useGencnUISuggestions } from "@/registry/new-york/gencn-ui/items/shared/hooks/use-gencn-ui-suggestions"; const defaultSuggestPrompt = "Generate complete, independent sentence suggestions based on the user's input. Each suggestion should start with the user sentence and be a full, grammatically correct sentence that relates to or expands on the user's text. complete the user's text - and provide alternative complete sentences that the user might want to use. Make each suggestion unique and meaningful."; export function SuggestionExample() { const [inputValue, setInputValue] = React.useState(""); const suggestTimeoutRef = React.useRef<NodeJS.Timeout | null>(null); // Use the new useGencnUISuggestions hook const { suggestions, isLoading, generate, reset, error, } = useGencnUISuggestions({ instructions: defaultSuggestPrompt, minSuggestions: 1, maxSuggestions: 3, available: "enabled", maxWordsPerSuggestion: 5, }); // Generate suggestions with debouncing const generateSuggestions = React.useCallback( async (text: string) => { const autoSuggestMinChars = 3; const autoSuggestDebounceMs = 500; if (!text.trim() || text.trim().length < autoSuggestMinChars) { reset(); return; } // Cancel any existing timeout if (suggestTimeoutRef.current) { clearTimeout(suggestTimeoutRef.current); } // Debounce the suggestion generation const timeoutId = setTimeout(async () => { try { // Pass the current text as context for generating related sentence suggestions await generate(`User's current text: "${text}"`); } catch (err) { console.error("Error generating suggestions:", err); } }, autoSuggestDebounceMs); suggestTimeoutRef.current = timeoutId; }, [generate, reset] ); // Handle input change const handleInputChange = React.useCallback( (e: React.ChangeEvent<HTMLInputElement>) => { const value = e.target.value; setInputValue(value); generateSuggestions(value); }, [generateSuggestions] ); // Handle suggestion click const handleSuggestionClick = React.useCallback( (suggestion: string) => { setInputValue(suggestion); reset(); }, [reset] ); // Cleanup on unmount React.useEffect(() => { return () => { if (suggestTimeoutRef.current) { clearTimeout(suggestTimeoutRef.current); } reset(); }; }, [reset]); return ( <div className="space-y-4"> <div className="space-y-2"> <Label htmlFor="suggestion-input" className="text-sm font-medium"> Type to get AI suggestions </Label> <Input id="suggestion-input" placeholder="Type something (at least 3 characters)..." value={inputValue} onChange={handleInputChange} className="w-full" /> </div> {(isLoading || suggestions.length > 0) && ( <div className="space-y-2"> <Label className="text-sm font-medium">Suggestions</Label> {isLoading && suggestions.length === 0 ? ( <div className="flex items-center gap-2 py-2"> <Spinner className="size-4" /> <span className="text-muted-foreground text-sm"> Generating suggestions... </span> </div> ) : ( <Suggestions> {suggestions.map((suggestion, index) => ( <Suggestion key={`${suggestion.text}-${index}`} onClick={handleSuggestionClick} suggestion={suggestion.text} /> ))} </Suggestions> )} </div> )} {error && <p className="text-destructive text-xs">Error: {error}</p>} </div> ); }
Server API
Path:
/api/suggestionsSource:
import { google, createGoogleGenerativeAI } from "@ai-sdk/google"; import { generateObject } from "ai"; import { z } from "zod"; // Allow streaming responses up to 30 seconds export const maxDuration = 30; interface SuggestionsRequest { prompt: string; schema: Record<string, unknown>; omitResponseConstraintInput?: boolean; LLM_API_KEY?: string; } // Convert JSON Schema to Zod schema function jsonSchemaToZod(schema: Record<string, unknown>): z.ZodTypeAny { if (!schema || typeof schema !== "object") { return z.any(); } if (schema.type === "object") { const shape: Record<string, z.ZodTypeAny> = {}; if (schema.properties) { for (const [key, prop] of Object.entries( schema.properties as Record<string, Record<string, unknown>> )) { const fieldSchema = jsonSchemaToZod(prop); // Make optional if not in required array if ( schema.required && Array.isArray(schema.required) && schema.required.includes(key) ) { shape[key] = fieldSchema; } else { shape[key] = fieldSchema.optional(); } } } return z.object(shape); } else if (schema.type === "array") { const itemSchema = schema.items && typeof schema.items === "object" && !Array.isArray(schema.items) ? jsonSchemaToZod(schema.items as Record<string, unknown>) : z.any(); let arraySchema = z.array(itemSchema); // Handle minItems and maxItems if (typeof schema.minItems === "number") { arraySchema = arraySchema.min(schema.minItems) as z.ZodArray<z.ZodTypeAny>; } if (typeof schema.maxItems === "number") { arraySchema = arraySchema.max(schema.maxItems) as z.ZodArray<z.ZodTypeAny>; } return arraySchema; } else if (schema.type === "string") { let stringSchema = z.string(); if (typeof schema.minLength === "number") { stringSchema = stringSchema.min(schema.minLength); } if (typeof schema.maxLength === "number") { stringSchema = stringSchema.max(schema.maxLength); } if (schema.enum && Array.isArray(schema.enum)) { return z.enum(schema.enum as [string, ...string[]]); } return stringSchema; } else if (schema.type === "number") { let numberSchema = z.number(); if (typeof schema.minimum === "number") { numberSchema = numberSchema.min(schema.minimum); } if (typeof schema.maximum === "number") { numberSchema = numberSchema.max(schema.maximum); } return numberSchema; } else if (schema.type === "boolean") { return z.boolean(); } else if (schema.type === "integer") { let intSchema = z.number().int(); if (typeof schema.minimum === "number") { intSchema = intSchema.min(schema.minimum); } if (typeof schema.maximum === "number") { intSchema = intSchema.max(schema.maximum); } return intSchema; } return z.any(); } export async function POST(req: Request) { try { const request: SuggestionsRequest = await req.json(); const { prompt, schema, LLM_API_KEY } = request; if (!prompt) { return new Response(JSON.stringify({ error: "Prompt is required" }), { status: 400, headers: { "Content-Type": "application/json" }, }); } if (!schema) { return new Response(JSON.stringify({ error: "Schema is required" }), { status: 400, headers: { "Content-Type": "application/json" }, }); } // Create provider instance with manual API key if provided, otherwise use default const googleProvider = LLM_API_KEY ? createGoogleGenerativeAI({ apiKey: LLM_API_KEY }) : google; // Convert JSON Schema to Zod schema const zodSchema = jsonSchemaToZod(schema); const result = await generateObject({ model: googleProvider("gemini-2.0-flash-exp"), schema: zodSchema, prompt, }); return new Response(JSON.stringify(result.object), { headers: { "Content-Type": "application/json" }, }); } catch (error) { console.error("Suggestions API error:", error); return new Response( JSON.stringify({ error: "Internal server error", message: (error as Error).message, }), { status: 500, headers: { "Content-Type": "application/json" }, } ); } }
Human Verification
GencnUI-powered human verification component that uses AI to generate verification instructions and verify selfies through Chrome's LanguageModel API.
Chat
The extended vercel ai-sdk react hook supporting on-device chrome and server llm keeping the same interface and functionality as the ai-sdk react.

