إضافة ميزات مدعومة بالـ AI
توليد المحتوى بالـ AI
5 دقيقة للقراءة
Project Goal: Add AI-powered content generation capabilities to automate writing, summarization, and data extraction.
Content Generation Use Cases
AI content generation powers:
- Summarization - Condense long documents into key points
- Translation - Convert content between languages
- Data extraction - Pull structured data from unstructured text
- Content expansion - Generate detailed content from outlines
- Rewriting - Adjust tone, style, or complexity
Project Setup Prompt
Add AI content generation to my application:
## Tech Stack
- Next.js 15 with App Router
- Vercel AI SDK v4
- Anthropic Claude for generation
- Zod for structured output validation
## Features
1. Document summarization (multiple lengths)
2. Multi-language translation
3. Structured data extraction
4. Content rewriting with style control
5. Batch processing for multiple documents
## Project Structure
/lib /generation summarize.ts # Summarization translate.ts # Translation extract.ts # Data extraction rewrite.ts # Style rewriting /app /api/generate summarize/route.ts translate/route.ts extract/route.ts
Summarization with Length Control
// lib/generation/summarize.ts
import { generateText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
export type SummaryLength = 'brief' | 'standard' | 'detailed';
interface SummarizeOptions {
length: SummaryLength;
format: 'paragraph' | 'bullets' | 'key-points';
focusOn?: string; // Optional focus area
}
const lengthInstructions: Record<SummaryLength, string> = {
brief: 'Create a 2-3 sentence summary capturing only the most essential points.',
standard: 'Create a summary of 1-2 paragraphs covering the main ideas and key details.',
detailed: 'Create a comprehensive summary preserving important nuances, examples, and supporting details.',
};
const formatInstructions: Record<string, string> = {
paragraph: 'Write in flowing paragraph form.',
bullets: 'Use bullet points for each main idea.',
'key-points': 'List numbered key points with brief explanations.',
};
export async function summarizeText(
text: string,
options: SummarizeOptions
): Promise<string> {
const { length, format, focusOn } = options;
const prompt = `Summarize the following text.
${lengthInstructions[length]}
${formatInstructions[format]}
${focusOn ? `Focus particularly on aspects related to: ${focusOn}` : ''}
Text to summarize:
${text}
Summary:`;
const result = await generateText({
model: anthropic('claude-sonnet-4-20250514'),
prompt,
maxTokens: length === 'brief' ? 200 : length === 'standard' ? 500 : 1000,
temperature: 0.3, // Lower temperature for more consistent summaries
});
return result.text;
}
// Batch summarization for multiple documents
export async function summarizeBatch(
documents: { id: string; text: string }[],
options: SummarizeOptions
): Promise<{ id: string; summary: string }[]> {
const results = await Promise.all(
documents.map(async (doc) => ({
id: doc.id,
summary: await summarizeText(doc.text, options),
}))
);
return results;
}
Summarization API Route
// app/api/generate/summarize/route.ts
import { summarizeText, type SummaryLength } from '@/lib/generation/summarize';
export async function POST(request: Request) {
try {
const { text, length = 'standard', format = 'paragraph', focusOn } =
await request.json();
if (!text) {
return Response.json({ error: 'Text is required' }, { status: 400 });
}
// Limit input text to prevent excessive token usage
const maxChars = 50000;
const truncatedText =
text.length > maxChars ? text.slice(0, maxChars) + '...' : text;
const summary = await summarizeText(truncatedText, {
length: length as SummaryLength,
format,
focusOn,
});
return Response.json({
summary,
inputLength: text.length,
truncated: text.length > maxChars,
});
} catch (error) {
console.error('Summarization error:', error);
return Response.json(
{ error: 'Summarization failed' },
{ status: 500 }
);
}
}
Translation with Quality Control
// lib/generation/translate.ts
import { generateText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
interface TranslateOptions {
sourceLanguage?: string; // Auto-detect if not provided
targetLanguage: string;
preserveFormatting?: boolean;
formalityLevel?: 'formal' | 'neutral' | 'informal';
domain?: string; // e.g., 'technical', 'legal', 'medical'
}
const languageNames: Record<string, string> = {
en: 'English',
ar: 'Arabic',
es: 'Spanish',
fr: 'French',
de: 'German',
zh: 'Chinese',
ja: 'Japanese',
ko: 'Korean',
pt: 'Portuguese',
ru: 'Russian',
};
export async function translateText(
text: string,
options: TranslateOptions
): Promise<{ translation: string; detectedLanguage?: string }> {
const {
sourceLanguage,
targetLanguage,
preserveFormatting = true,
formalityLevel = 'neutral',
domain,
} = options;
const targetLangName = languageNames[targetLanguage] || targetLanguage;
const sourceLangName = sourceLanguage
? languageNames[sourceLanguage] || sourceLanguage
: null;
const prompt = `You are a professional translator.
Task: Translate the following text ${sourceLangName ? `from ${sourceLangName}` : ''} to ${targetLangName}.
Guidelines:
- Maintain the original meaning and intent
- Use ${formalityLevel} register/tone
${preserveFormatting ? '- Preserve the original formatting (paragraphs, lists, etc.)' : ''}
${domain ? `- This is ${domain} content, use appropriate terminology` : ''}
${!sourceLanguage ? '- First detect the source language and include it in your response' : ''}
Text to translate:
${text}
${!sourceLanguage ? 'Detected language: [language]\n' : ''}Translation:`;
const result = await generateText({
model: anthropic('claude-sonnet-4-20250514'),
prompt,
maxTokens: Math.min(text.length * 3, 4000), // Account for expansion
temperature: 0.3,
});
// Parse detected language if auto-detection was used
let detectedLanguage: string | undefined;
let translation = result.text;
if (!sourceLanguage) {
const langMatch = result.text.match(/Detected language:\s*(\w+)/i);
if (langMatch) {
detectedLanguage = langMatch[1].toLowerCase();
translation = result.text.replace(/Detected language:.*\n/i, '').trim();
}
}
return { translation, detectedLanguage };
}
Structured Data Extraction
// lib/generation/extract.ts
import { generateObject } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
import { z } from 'zod';
// Contact information extraction
const contactSchema = z.object({
name: z.string().optional(),
email: z.string().email().optional(),
phone: z.string().optional(),
company: z.string().optional(),
title: z.string().optional(),
address: z
.object({
street: z.string().optional(),
city: z.string().optional(),
state: z.string().optional(),
country: z.string().optional(),
postalCode: z.string().optional(),
})
.optional(),
});
export async function extractContact(text: string) {
const result = await generateObject({
model: anthropic('claude-sonnet-4-20250514'),
schema: contactSchema,
prompt: `Extract contact information from the following text.
Only include fields that are explicitly mentioned.
Text:
${text}`,
});
return result.object;
}
// Event extraction
const eventSchema = z.object({
title: z.string(),
date: z.string().optional(),
time: z.string().optional(),
location: z.string().optional(),
description: z.string().optional(),
attendees: z.array(z.string()).optional(),
type: z.enum(['meeting', 'conference', 'webinar', 'social', 'other']).optional(),
});
export async function extractEvent(text: string) {
const result = await generateObject({
model: anthropic('claude-sonnet-4-20250514'),
schema: eventSchema,
prompt: `Extract event information from the following text.
Text:
${text}`,
});
return result.object;
}
// Invoice/Receipt extraction
const invoiceSchema = z.object({
vendor: z.string(),
invoiceNumber: z.string().optional(),
date: z.string().optional(),
dueDate: z.string().optional(),
items: z.array(
z.object({
description: z.string(),
quantity: z.number().optional(),
unitPrice: z.number().optional(),
total: z.number().optional(),
})
),
subtotal: z.number().optional(),
tax: z.number().optional(),
total: z.number(),
currency: z.string().default('USD'),
});
export async function extractInvoice(text: string) {
const result = await generateObject({
model: anthropic('claude-sonnet-4-20250514'),
schema: invoiceSchema,
prompt: `Extract invoice/receipt information from the following text.
Parse amounts as numbers without currency symbols.
Text:
${text}`,
});
return result.object;
}
// Generic extraction with custom schema
export async function extractStructured<T extends z.ZodType>(
text: string,
schema: T,
instructions?: string
): Promise<z.infer<T>> {
const result = await generateObject({
model: anthropic('claude-sonnet-4-20250514'),
schema,
prompt: `${instructions || 'Extract structured data from the following text.'}
Text:
${text}`,
});
return result.object;
}
Content Rewriting
// lib/generation/rewrite.ts
import { generateText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
export type WritingStyle =
| 'professional'
| 'casual'
| 'academic'
| 'creative'
| 'technical'
| 'simplified';
export type ToneAdjustment =
| 'more-formal'
| 'more-friendly'
| 'more-confident'
| 'more-empathetic'
| 'neutral';
interface RewriteOptions {
style?: WritingStyle;
tone?: ToneAdjustment;
targetAudience?: string;
lengthAdjustment?: 'shorter' | 'same' | 'longer';
preserveKeyPoints?: boolean;
}
const styleInstructions: Record<WritingStyle, string> = {
professional: 'Use clear, professional language suitable for business communication.',
casual: 'Use conversational, friendly language as if talking to a peer.',
academic: 'Use formal academic language with proper citations format if needed.',
creative: 'Use engaging, creative language with vivid descriptions.',
technical: 'Use precise technical terminology and structured explanations.',
simplified: 'Use simple words and short sentences, suitable for a general audience.',
};
const toneInstructions: Record<ToneAdjustment, string> = {
'more-formal': 'Increase formality, remove contractions and colloquialisms.',
'more-friendly': 'Make the tone warmer and more approachable.',
'more-confident': 'Use assertive language, remove hedging words like "maybe" or "might".',
'more-empathetic': 'Show understanding and consideration for the reader.',
neutral: 'Maintain a balanced, objective tone.',
};
export async function rewriteText(
text: string,
options: RewriteOptions = {}
): Promise<string> {
const {
style = 'professional',
tone = 'neutral',
targetAudience,
lengthAdjustment = 'same',
preserveKeyPoints = true,
} = options;
const lengthInstruction =
lengthAdjustment === 'shorter'
? 'Make the text more concise, about 50-70% of the original length.'
: lengthAdjustment === 'longer'
? 'Expand the text with more detail and examples.'
: 'Keep approximately the same length.';
const prompt = `Rewrite the following text according to these guidelines:
Style: ${styleInstructions[style]}
Tone: ${toneInstructions[tone]}
Length: ${lengthInstruction}
${targetAudience ? `Target audience: ${targetAudience}` : ''}
${preserveKeyPoints ? 'Preserve all key information and main points.' : ''}
Original text:
${text}
Rewritten text:`;
const result = await generateText({
model: anthropic('claude-sonnet-4-20250514'),
prompt,
maxTokens: lengthAdjustment === 'longer' ? text.length * 2 : text.length * 1.5,
temperature: 0.5,
});
return result.text;
}
Generation API Routes
// app/api/generate/extract/route.ts
import {
extractContact,
extractEvent,
extractInvoice,
} from '@/lib/generation/extract';
type ExtractionType = 'contact' | 'event' | 'invoice';
export async function POST(request: Request) {
try {
const { text, type } = (await request.json()) as {
text: string;
type: ExtractionType;
};
if (!text || !type) {
return Response.json(
{ error: 'Text and type are required' },
{ status: 400 }
);
}
let result;
switch (type) {
case 'contact':
result = await extractContact(text);
break;
case 'event':
result = await extractEvent(text);
break;
case 'invoice':
result = await extractInvoice(text);
break;
default:
return Response.json(
{ error: 'Invalid extraction type' },
{ status: 400 }
);
}
return Response.json({ type, data: result });
} catch (error) {
console.error('Extraction error:', error);
return Response.json({ error: 'Extraction failed' }, { status: 500 });
}
}
Content Generation UI
// components/generation/summarizer.tsx
'use client';
import { useState } from 'react';
import { Loader2 } from 'lucide-react';
export function Summarizer() {
const [text, setText] = useState('');
const [summary, setSummary] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [length, setLength] = useState<'brief' | 'standard' | 'detailed'>('standard');
const [format, setFormat] = useState<'paragraph' | 'bullets' | 'key-points'>('paragraph');
const handleSummarize = async () => {
if (!text.trim()) return;
setIsLoading(true);
try {
const response = await fetch('/api/generate/summarize', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text, length, format }),
});
const data = await response.json();
setSummary(data.summary);
} catch (error) {
console.error('Summarization failed:', error);
} finally {
setIsLoading(false);
}
};
return (
<div className="space-y-4">
<div className="flex gap-4">
<select
value={length}
onChange={(e) => setLength(e.target.value as any)}
className="rounded-md border px-3 py-2"
>
<option value="brief">Brief (2-3 sentences)</option>
<option value="standard">Standard (1-2 paragraphs)</option>
<option value="detailed">Detailed (comprehensive)</option>
</select>
<select
value={format}
onChange={(e) => setFormat(e.target.value as any)}
className="rounded-md border px-3 py-2"
>
<option value="paragraph">Paragraph</option>
<option value="bullets">Bullet Points</option>
<option value="key-points">Key Points</option>
</select>
</div>
<textarea
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Paste text to summarize..."
className="h-48 w-full rounded-lg border p-4"
/>
<button
onClick={handleSummarize}
disabled={isLoading || !text.trim()}
className="flex items-center gap-2 rounded-lg bg-primary px-4 py-2 text-primary-foreground"
>
{isLoading && <Loader2 className="h-4 w-4 animate-spin" />}
Summarize
</button>
{summary && (
<div className="rounded-lg border bg-muted p-4">
<h3 className="mb-2 font-semibold">Summary</h3>
<div className="whitespace-pre-wrap">{summary}</div>
</div>
)}
</div>
);
}
Key Takeaways
- Structured output with Zod ensures type-safe extraction
- Temperature control affects creativity vs consistency
- Prompt engineering is crucial for quality generation
- Length limits prevent excessive API costs
- Batch processing improves efficiency for multiple documents
توليد المحتوى بالـ AI
هدف المشروع: إضافة قدرات توليد المحتوى المدعومة بالـ AI لأتمتة الكتابة والتلخيص واستخراج البيانات.
حالات استخدام توليد المحتوى
توليد محتوى AI يشغّل:
- التلخيص - تكثيف المستندات الطويلة إلى نقاط رئيسية
- الترجمة - تحويل المحتوى بين اللغات
- استخراج البيانات - سحب بيانات مهيكلة من نص غير مهيكل
- توسيع المحتوى - توليد محتوى مفصل من المخططات
- إعادة الكتابة - ضبط النبرة أو الأسلوب أو التعقيد
التلخيص مع التحكم بالطول
// lib/generation/summarize.ts
import { generateText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
export type SummaryLength = 'brief' | 'standard' | 'detailed';
interface SummarizeOptions {
length: SummaryLength;
format: 'paragraph' | 'bullets' | 'key-points';
focusOn?: string;
}
const lengthInstructions: Record<SummaryLength, string> = {
brief: 'أنشئ ملخصاً من 2-3 جمل يلتقط النقاط الأساسية فقط.',
standard: 'أنشئ ملخصاً من 1-2 فقرة يغطي الأفكار الرئيسية والتفاصيل المهمة.',
detailed: 'أنشئ ملخصاً شاملاً يحافظ على الفروق الدقيقة والأمثلة والتفاصيل الداعمة.',
};
export async function summarizeText(
text: string,
options: SummarizeOptions
): Promise<string> {
const { length, format, focusOn } = options;
const prompt = `لخّص النص التالي.
${lengthInstructions[length]}
${format === 'paragraph' ? 'اكتب بشكل فقرات متدفقة.' : format === 'bullets' ? 'استخدم نقاطاً لكل فكرة رئيسية.' : 'اذكر نقاطاً مرقمة مع شروحات موجزة.'}
${focusOn ? `ركز خاصة على الجوانب المتعلقة بـ: ${focusOn}` : ''}
النص المراد تلخيصه:
${text}
الملخص:`;
const result = await generateText({
model: anthropic('claude-sonnet-4-20250514'),
prompt,
maxTokens: length === 'brief' ? 200 : length === 'standard' ? 500 : 1000,
temperature: 0.3,
});
return result.text;
}
استخراج البيانات المهيكلة
// lib/generation/extract.ts
import { generateObject } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
import { z } from 'zod';
// استخراج معلومات الاتصال
const contactSchema = z.object({
name: z.string().optional(),
email: z.string().email().optional(),
phone: z.string().optional(),
company: z.string().optional(),
title: z.string().optional(),
address: z
.object({
street: z.string().optional(),
city: z.string().optional(),
state: z.string().optional(),
country: z.string().optional(),
postalCode: z.string().optional(),
})
.optional(),
});
export async function extractContact(text: string) {
const result = await generateObject({
model: anthropic('claude-sonnet-4-20250514'),
schema: contactSchema,
prompt: `استخرج معلومات الاتصال من النص التالي.
أدرج فقط الحقول المذكورة صراحة.
النص:
${text}`,
});
return result.object;
}
// استخراج الحدث
const eventSchema = z.object({
title: z.string(),
date: z.string().optional(),
time: z.string().optional(),
location: z.string().optional(),
description: z.string().optional(),
attendees: z.array(z.string()).optional(),
type: z.enum(['meeting', 'conference', 'webinar', 'social', 'other']).optional(),
});
export async function extractEvent(text: string) {
const result = await generateObject({
model: anthropic('claude-sonnet-4-20250514'),
schema: eventSchema,
prompt: `استخرج معلومات الحدث من النص التالي.
النص:
${text}`,
});
return result.object;
}
// استخراج عام مع مخطط مخصص
export async function extractStructured<T extends z.ZodType>(
text: string,
schema: T,
instructions?: string
): Promise<z.infer<T>> {
const result = await generateObject({
model: anthropic('claude-sonnet-4-20250514'),
schema,
prompt: `${instructions || 'استخرج بيانات مهيكلة من النص التالي.'}
النص:
${text}`,
});
return result.object;
}
إعادة كتابة المحتوى
// lib/generation/rewrite.ts
import { generateText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
export type WritingStyle =
| 'professional'
| 'casual'
| 'academic'
| 'creative'
| 'technical'
| 'simplified';
interface RewriteOptions {
style?: WritingStyle;
targetAudience?: string;
lengthAdjustment?: 'shorter' | 'same' | 'longer';
}
const styleInstructions: Record<WritingStyle, string> = {
professional: 'استخدم لغة واضحة ومهنية مناسبة للتواصل التجاري.',
casual: 'استخدم لغة محادثة ودودة كما لو تتحدث مع زميل.',
academic: 'استخدم لغة أكاديمية رسمية مع تنسيق اقتباسات مناسب إذا لزم.',
creative: 'استخدم لغة جذابة وإبداعية مع أوصاف حية.',
technical: 'استخدم مصطلحات تقنية دقيقة وشروحات منظمة.',
simplified: 'استخدم كلمات بسيطة وجمل قصيرة، مناسبة لجمهور عام.',
};
export async function rewriteText(
text: string,
options: RewriteOptions = {}
): Promise<string> {
const {
style = 'professional',
targetAudience,
lengthAdjustment = 'same',
} = options;
const lengthInstruction =
lengthAdjustment === 'shorter'
? 'اجعل النص أكثر إيجازاً، حوالي 50-70% من الطول الأصلي.'
: lengthAdjustment === 'longer'
? 'وسّع النص بمزيد من التفاصيل والأمثلة.'
: 'حافظ على نفس الطول تقريباً.';
const prompt = `أعد كتابة النص التالي وفقاً لهذه الإرشادات:
الأسلوب: ${styleInstructions[style]}
الطول: ${lengthInstruction}
${targetAudience ? `الجمهور المستهدف: ${targetAudience}` : ''}
حافظ على جميع المعلومات والنقاط الرئيسية.
النص الأصلي:
${text}
النص المعاد كتابته:`;
const result = await generateText({
model: anthropic('claude-sonnet-4-20250514'),
prompt,
maxTokens: lengthAdjustment === 'longer' ? text.length * 2 : text.length * 1.5,
temperature: 0.5,
});
return result.text;
}
النقاط الرئيسية
- المخرجات المهيكلة مع Zod تضمن استخراجاً آمن الأنواع
- التحكم بالحرارة يؤثر على الإبداع مقابل الاتساق
- هندسة البرومبت حاسمة لجودة التوليد
- حدود الطول تمنع تكاليف API المفرطة
- المعالجة بالدفعات تحسن الكفاءة لمستندات متعددة