import { useState } from 'react'
import { createFileRoute } from '@tanstack/react-router'
import { ChefHat, Clock, Users, Gauge } from 'lucide-react'
import { Streamdown } from 'streamdown'
import type { Recipe } from './api.ai.structured'
type Mode = 'structured' | 'oneshot'
const SAMPLE_RECIPES = [
'Homemade Margherita Pizza',
'Thai Green Curry',
'Classic Beef Bourguignon',
'Chocolate Lava Cake',
'Crispy Korean Fried Chicken',
'Fresh Spring Rolls with Peanut Sauce',
'Creamy Mushroom Risotto',
'Authentic Pad Thai',
]
function RecipeCard({ recipe }: { recipe: Recipe }) {
const difficultyColors = {
easy: 'demo-pill',
medium: 'demo-pill',
hard: 'demo-pill',
}
return (
{/* Header */}
{recipe.name}
{recipe.description}
{/* Meta info */}
Prep: {recipe.prepTime}
Cook: {recipe.cookTime}
{recipe.servings} servings
{recipe.difficulty}
{/* Ingredients */}
Ingredients
{recipe.ingredients.map((ing, idx) => (
-
•
{ing.amount} {ing.item}
{ing.notes && ({ing.notes})}
))}
{/* Instructions */}
Instructions
{recipe.instructions.map((step, idx) => (
-
{idx + 1}
{step}
))}
{/* Tips */}
{recipe.tips && recipe.tips.length > 0 && (
Tips
{recipe.tips.map((tip, idx) => (
-
*
{tip}
))}
)}
{/* Nutrition */}
{recipe.nutritionPerServing && (
Nutrition (per serving)
{recipe.nutritionPerServing.calories && (
{recipe.nutritionPerServing.calories} cal
)}
{recipe.nutritionPerServing.protein && (
Protein: {recipe.nutritionPerServing.protein}
)}
{recipe.nutritionPerServing.carbs && (
Carbs: {recipe.nutritionPerServing.carbs}
)}
{recipe.nutritionPerServing.fat && (
Fat: {recipe.nutritionPerServing.fat}
)}
)}
)
}
function StructuredPage() {
const [recipeName, setRecipeName] = useState('')
const [result, setResult] = useState<{
mode: Mode
recipe?: Recipe
markdown?: string
provider: string
model: string
} | null>(null)
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState(null)
const handleGenerate = async (mode: Mode) => {
if (!recipeName.trim()) return
setIsLoading(true)
setError(null)
setResult(null)
try {
const response = await fetch('/demo/api/ai/structured', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ recipeName, mode }),
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || 'Failed to generate recipe')
}
setResult(data)
} catch (err: any) {
setError(err.message)
} finally {
setIsLoading(false)
}
}
const canExecute = !!(!isLoading && recipeName.trim() && !error)
return (
One-Shot & Structured Output
Compare two output modes:{' '}
One-Shot returns
freeform markdown, while{' '}
Structured returns
validated JSON conforming to a Zod schema.
Generated Recipe
{result && (
{result.mode === 'structured' ? 'Structured JSON' : 'Markdown'}
)}
{error && (
{error}
)}
{result ? (
{result.mode === 'structured' && result.recipe ? (
) : result.markdown ? (
{result.markdown}
) : null}
) : !error && !isLoading ? (
Enter a recipe name and click "Generate Recipe" to get started.
) : null}
)
}
export const Route = createFileRoute('/demo/ai-structured')({
component: StructuredPage,
})