İçeriğe geç
Sedat Demir
Geri dön

Claude API ile Akıllı Yazı Üretimi: React Uygulamalarında Yapay Zeka Destekli İçerik Oluşturma Rehberi

Claude API ile Akıllı Yazı Üretimi: React Uygulamalarında Yapay Zeka Destekli İçerik Oluşturma Rehberi

Yapay zeka destekli içerik üretimi, modern web uygulamalarının vazgeçilmez bir parçası haline geldi. Anthropic'in geliştirdiği Claude API, güçlü dil modeli yetenekleriyle geliştiricilere blog yazıları, ürün açıklamaları, e-posta taslakları ve çok daha fazlasını programatik olarak üretme imkânı sunuyor. Bu yazıda, Claude API'yi bir React uygulamasına nasıl entegre edeceğinizi, streaming yanıtlarla gerçek zamanlı içerik üretimini nasıl yapacağınızı ve production ortamında dikkat etmeniz gereken her şeyi detaylıca ele alacağız.


Claude API Nedir ve Neden Tercih Etmelisiniz?

Claude, Anthropic tarafından geliştirilen büyük bir dil modelidir (LLM). GPT serisine alternatif olarak öne çıkan Claude, özellikle şu avantajlarıyla dikkat çeker:


Geliştirme Ortamının Hazırlanması

1. API Anahtarı Edinme

İlk adım olarak Anthropic Console üzerinden bir hesap oluşturun ve API anahtarınızı alın. Anahtarınızı güvenli bir şekilde saklayın — asla frontend kodunda doğrudan kullanmayın.

2. Proje Kurulumu

Yeni bir React projesi oluşturup gerekli bağımlılıkları yükleyelim:

npx create-react-app claude-writer --template typescript
cd claude-writer
npm install @anthropic-ai/sdk express cors dotenv
npm install -D @types/express @types/cors nodemon concurrently

3. Ortam Değişkenlerini Yapılandırma

Proje kök dizininde bir .env dosyası oluşturun:

ANTHROPIC_API_KEY=sk-ant-api03-xxxxxxxxxxxxxxxxxxxx
PORT=3001

⚠️ Güvenlik Uyarısı: .env dosyanızı mutlaka .gitignore dosyasına ekleyin. API anahtarınız asla versiyon kontrol sistemine commit edilmemelidir.


Backend API Sunucusu Oluşturma

Claude API'ye doğrudan frontend'den istek göndermek güvenlik açığı oluşturur. Bu nedenle bir proxy backend oluşturmamız gerekiyor.

Express Sunucusu

server/index.js dosyasını oluşturun:

const express = require('express');
const cors = require('cors');
const Anthropic = require('@anthropic-ai/sdk');
require('dotenv').config();

const app = express();
app.use(cors({ origin: 'http://localhost:3000' }));
app.use(express.json());

const anthropic = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY,
});

// Standart yazı üretimi endpoint'i
app.post('/api/generate', async (req, res) => {
  try {
    const { prompt, contentType, tone, language, maxTokens } = req.body;

    if (!prompt || prompt.trim().length === 0) {
      return res.status(400).json({ error: 'Prompt alanı zorunludur.' });
    }

    const systemPrompt = buildSystemPrompt(contentType, tone, language);

    const message = await anthropic.messages.create({
      model: 'claude-sonnet-4-20250514',
      max_tokens: maxTokens || 1024,
      system: systemPrompt,
      messages: [
        {
          role: 'user',
          content: prompt,
        },
      ],
    });

    res.json({
      content: message.content[0].text,
      usage: {
        inputTokens: message.usage.input_tokens,
        outputTokens: message.usage.output_tokens,
      },
      model: message.model,
    });
  } catch (error) {
    console.error('Claude API Hatası:', error.message);
    res.status(500).json({
      error: 'İçerik üretimi sırasında bir hata oluştu.',
      details: error.message,
    });
  }
});

function buildSystemPrompt(contentType, tone, language) {
  const toneMap = {
    professional: 'profesyonel ve resmi',
    casual: 'samimi ve günlük',
    academic: 'akademik ve bilimsel',
    creative: 'yaratıcı ve etkileyici',
  };

  const contentTypeMap = {
    blog: 'SEO dostu blog yazısı',
    product: 'ürün açıklaması',
    email: 'e-posta taslağı',
    social: 'sosyal medya paylaşımı',
    summary: 'özet',
  };

  return `Sen deneyimli bir ${language || 'Türkçe'} içerik yazarısın. 
${contentTypeMap[contentType] || 'genel içerik'} formatında, 
${toneMap[tone] || 'profesyonel'} bir üslupla yazı üretiyorsun. 
Markdown formatında, okunabilir ve akıcı içerikler oluştur. 
Her zaman doğru bilgi ver ve gerektiğinde alt başlıklar kullan.`;
}

const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
  console.log(`✅ Sunucu http://localhost:${PORT} adresinde çalışıyor`);
});

Streaming ile Gerçek Zamanlı İçerik Akışı

Kullanıcı deneyimini dramatik şekilde iyileştiren streaming özelliği, içeriğin parça parça aktarılmasını sağlar. Kullanıcı, tüm yanıtın oluşturulmasını beklemek yerine, metnin gerçek zamanlı olarak yazıldığını görür.

Streaming Endpoint

// server/index.js dosyasına ekleyin
app.post('/api/generate-stream', async (req, res) => {
  try {
    const { prompt, contentType, tone, language, maxTokens } = req.body;

    // SSE (Server-Sent Events) başlıkları
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');

    const systemPrompt = buildSystemPrompt(contentType, tone, language);

    const stream = await anthropic.messages.stream({
      model: 'claude-sonnet-4-20250514',
      max_tokens: maxTokens || 2048,
      system: systemPrompt,
      messages: [
        {
          role: 'user',
          content: prompt,
        },
      ],
    });

    for await (const event of stream) {
      if (
        event.type === 'content_block_delta' &&
        event.delta.type === 'text_delta'
      ) {
        res.write(`data: ${JSON.stringify({ text: event.delta.text })}\n\n`);
      }
    }

    // Akışın bittiğini bildir
    const finalMessage = await stream.finalMessage();
    res.write(
      `data: ${JSON.stringify({
        done: true,
        usage: {
          inputTokens: finalMessage.usage.input_tokens,
          outputTokens: finalMessage.usage.output_tokens,
        },
      })}\n\n`
    );

    res.end();
  } catch (error) {
    console.error('Streaming hatası:', error.message);
    res.write(`data: ${JSON.stringify({ error: error.message })}\n\n`);
    res.end();
  }
});

React Frontend Uygulaması

Şimdi kullanıcıların yazı parametrelerini belirleyip içerik üretebileceği bir React arayüzü oluşturalım.

Custom Hook: useClaudeWriter

// src/hooks/useClaudeWriter.ts
import { useState, useCallback, useRef } from 'react';

interface GenerateOptions {
  prompt: string;
  contentType: 'blog' | 'product' | 'email' | 'social' | 'summary';
  tone: 'professional' | 'casual' | 'academic' | 'creative';
  language: string;
  maxTokens: number;
}

interface UsageInfo {
  inputTokens: number;
  outputTokens: number;
}

export function useClaudeWriter() {
  const [content, setContent] = useState<string>('');
  const [isLoading, setIsLoading] = useState<boolean>(false);
  const [error, setError] = useState<string | null>(null);
  const [usage, setUsage] = useState<UsageInfo | null>(null);
  const abortControllerRef = useRef<AbortController | null>(null);

  const generateStream = useCallback(async (options: GenerateOptions) => {
    setIsLoading(true);
    setError(null);
    setContent('');
    setUsage(null);

    // Önceki isteği iptal et
    if (abortControllerRef.current) {
      abortControllerRef.current.abort();
    }

    const abortController = new AbortController();
    abortControllerRef.current = abortController;

    try {
      const response = await fetch('http://localhost:3001/api/generate-stream', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(options),
        signal: abortController.signal,
      });

      if (!response.ok) {
        throw new Error(`HTTP Hatası: ${response.status}`);
      }

      const reader = response.body?.getReader();
      const decoder = new TextDecoder();

      if (!reader) {
        throw new Error('Stream okunamıyor');
      }

      let accumulatedContent = '';

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        const chunk = decoder.decode(value, { stream: true });
        const lines = chunk.split('\n');

        for (const line of lines) {
          if (line.startsWith('data: ')) {
            try {
              const data = JSON.parse(line.slice(6));

              if (data.error) {
                setError(data.error);
                break;
              }

              if (data.done) {
                setUsage(data.usage);
                break;
              }

              if (data.text) {
                accumulatedContent += data.text;
                setContent(accumulatedContent);
              }
            } catch {
              // JSON parse hatalarını yoksay (eksik parçalar)
            }
          }
        }
      }
    } catch (err: any) {
      if (err.name !== 'AbortError') {
        setError(err.message || 'Bilinmeyen bir hata oluştu');
      }
    } finally {
      setIsLoading(false);
    }
  }, []);

  const cancel = useCallback(() => {
    if (abortControllerRef.current) {
      abortControllerRef.current.abort();
      setIsLoading(false);
    }
  }, []);

  const reset = useCallback(() => {
    setContent('');
    setError(null);
    setUsage(null);
  }, []);

  return {
    content,
    isLoading,
    error,
    usage,
    generateStream,
    cancel,
    reset,
  };
}

Ana Bileşen: ClaudeWriter

// src/components/ClaudeWriter.tsx
import React, { useState } from 'react';
import { useClaudeWriter } from '../hooks/useClaudeWriter';
import ReactMarkdown from 'react-markdown';

export function ClaudeWriter() {
  const [prompt, setPrompt] = useState('');
  const [contentType, setContentType] = useState<'blog' | 'product' | 'email' | 'social' | 'summary'>('blog');
  const [tone, setTone] = useState<'professional' | 'casual' | 'academic' | 'creative'>('professional');
  const [maxTokens, setMaxTokens] = useState(1024);

  const {
    content,
    isLoading,
    error,
    usage,
    generateStream,
    cancel,
    reset,
  } = useClaudeWriter();

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    if (!prompt.trim()) return;

    await generateStream({
      prompt,
      contentType,
      tone,
      language: 'Türkçe',
      maxTokens,
    });
  };

  const estimatedCost = usage
    ? (
        (usage.inputTokens * 0.003 + usage.outputTokens * 0.015) /
        1000
      ).toFixed(4)
    : null;

  return (
    <div className="claude-writer">
      <h1>🤖 Claude Yazı Üretici</h1>

      <form onSubmit={handleSubmit} className="writer-form">
        <div className="form-group">
          <label htmlFor="prompt">Ne hakkında yazayım?</label>
          <textarea
            id="prompt"
            value={prompt}
            onChange={(e) => setPrompt(e.target.value)}
            placeholder="Örn: React 19'un yeni özellikleri hakkında detaylı bir blog yazısı yaz..."
            rows={4}
            disabled={isLoading}
          />
        </div>

        <div className="form-

Share this post on:

Sonraki Yazı
LLM'lerde Prompt Injection Saldırıları ve Korunma Yöntemleri: Kapsamlı Rehber