Files
gh-jezweb-claude-skills-ski…/templates/nextjs-server-action.ts
2025-11-30 08:23:50 +08:00

151 lines
4.0 KiB
TypeScript

// Next.js Server Action with AI SDK
// AI SDK Core - Server Actions for Next.js App Router
'use server';
import { generateObject, generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
// Example 1: Simple text generation
export async function generateStory(theme: string) {
const result = await generateText({
model: openai('gpt-4-turbo'),
prompt: `Write a short story about: ${theme}`,
maxOutputTokens: 500,
});
return result.text;
}
// Example 2: Structured output (recipe generation)
export async function generateRecipe(ingredients: string[]) {
const RecipeSchema = z.object({
name: z.string(),
description: z.string(),
ingredients: z.array(
z.object({
name: z.string(),
amount: z.string(),
})
),
instructions: z.array(z.string()),
cookingTime: z.number().describe('Cooking time in minutes'),
servings: z.number(),
});
const result = await generateObject({
model: openai('gpt-4'),
schema: RecipeSchema,
prompt: `Create a recipe using these ingredients: ${ingredients.join(', ')}`,
});
return result.object;
}
// Example 3: Data extraction
export async function extractContactInfo(text: string) {
const ContactSchema = z.object({
name: z.string().optional(),
email: z.string().email().optional(),
phone: z.string().optional(),
company: z.string().optional(),
});
const result = await generateObject({
model: openai('gpt-4'),
schema: ContactSchema,
prompt: `Extract contact information from this text: ${text}`,
});
return result.object;
}
// Example 4: Error handling in Server Action
export async function generateWithErrorHandling(prompt: string) {
try {
const result = await generateText({
model: openai('gpt-4-turbo'),
prompt,
maxOutputTokens: 200,
});
return { success: true, data: result.text };
} catch (error: any) {
console.error('AI generation error:', error);
return {
success: false,
error: 'Failed to generate response. Please try again.',
};
}
}
/*
* Usage in Client Component:
*
* 'use client';
*
* import { useState } from 'react';
* import { generateStory, generateRecipe } from './actions';
*
* export default function AIForm() {
* const [result, setResult] = useState('');
* const [loading, setLoading] = useState(false);
*
* async function handleGenerateStory(formData: FormData) {
* setLoading(true);
* const theme = formData.get('theme') as string;
* const story = await generateStory(theme);
* setResult(story);
* setLoading(false);
* }
*
* async function handleGenerateRecipe(formData: FormData) {
* setLoading(true);
* const ingredients = (formData.get('ingredients') as string).split(',');
* const recipe = await generateRecipe(ingredients);
* setResult(JSON.stringify(recipe, null, 2));
* setLoading(false);
* }
*
* return (
* <div>
* <form action={handleGenerateStory}>
* <input name="theme" placeholder="Story theme" required />
* <button disabled={loading}>Generate Story</button>
* </form>
*
* <form action={handleGenerateRecipe}>
* <input name="ingredients" placeholder="flour, eggs, sugar" required />
* <button disabled={loading}>Generate Recipe</button>
* </form>
*
* {result && <pre>{result}</pre>}
* </div>
* );
* }
*/
/*
* File Structure:
*
* app/
* ├── actions.ts # This file (Server Actions)
* ├── page.tsx # Client component using actions
* └── api/
* └── chat/
* └── route.ts # Alternative: API Route for streaming
*
* Note: Server Actions are recommended for mutations and non-streaming AI calls.
* For streaming, use API Routes with streamText().toDataStreamResponse()
*/
/*
* Environment Variables (.env.local):
*
* OPENAI_API_KEY=sk-...
* ANTHROPIC_API_KEY=sk-ant-...
* GOOGLE_GENERATIVE_AI_API_KEY=...
*/