إضافة ميزات مدعومة بالـ 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

  1. Structured output with Zod ensures type-safe extraction
  2. Temperature control affects creativity vs consistency
  3. Prompt engineering is crucial for quality generation
  4. Length limits prevent excessive API costs
  5. 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;
}

النقاط الرئيسية

  1. المخرجات المهيكلة مع Zod تضمن استخراجاً آمن الأنواع
  2. التحكم بالحرارة يؤثر على الإبداع مقابل الاتساق
  3. هندسة البرومبت حاسمة لجودة التوليد
  4. حدود الطول تمنع تكاليف API المفرطة
  5. المعالجة بالدفعات تحسن الكفاءة لمستندات متعددة

اختبار

الوحدة 4: إضافة ميزات مدعومة بالـ AI

خذ الاختبار