Ana içeriğe atla

Perplexity Search API Eğitimi: Tarayıcınızda Gerçek Zamanlı İddia Doğrulayıcı Oluşturun

Perplexity Search API’yi kullanarak gerçek zamanlı olarak olgusal iddiaları doğrulayan bir Chrome uzantısı oluşturmayı öğrenin. Node.js backend kurulumu ve API entegrasyon rehberi dahildir.
Güncel 3 Eki 2026  · 10 dk. oku

Yapay Zeka ile Keşfedin

ChatGPTClaudePerplexity

Perplexity kısa süre önce, tam sayfalar yerine sıralanmış, bağlam açısından zengin parçalar döndüren, ayrıntılı web erişimi için tasarlanmış Perplexity Search API’sini yayınladı. Gerçek-zamanlı doğruluk kontrolü, araştırma ve RAG hatları gibi hız ve hassasiyetin önemli olduğu kullanımlar için geliştirildi.

Bu eğitimde, Claim Checker adlı, herhangi bir web sayfasındaki olgusal ifadeleri gerçek zamanlı olarak (geri getirilen web kanıtlarına dayanarak) doğrulayan bir tarayıcı uzantısı oluşturacağız. Perplexity Search API’nin sürekli yenilenen indeksinin ve düşük gecikmeli erişim katmanının; anlamsal sinyaller ve çok-aşamalı yeniden sıralama ile birleştiğinde, en ilgili kanıtları hızla bulmayı nasıl kolaylaştırdığını göreceksiniz. Bu süreçte şunları öğreneceksiniz:

  • Perplexity Search API’yi kullanarak web’den yüksek kaliteli, sıralanmış kanıtlar çekmek ve arama sonuçlarını özel sıralama algoritmalarıyla işlemek.
  • API anahtarınızı güvenle yönetmek, CORS isteklerini ele almak ve alan adı çıkarımı ile tarihe dayalı sıralamayı içeren akıllı sonuç işleme uygulamak için bir Node.js backend geliştirmek.
  • Metin seçimini, yan panel arayüzlerini ve tarayıcı ile backend sunucunuz arasında sorunsuz iletişimi etkinleştiren içerik komut dosyalarına sahip bir Chrome/Edge uzantısı oluşturmak.
  • Kaynak URL’leri, başlıklar, alıntılar, alan adları ve yayın tarihleri dahil doğrulama sonuçlarını, gerçek zamanlı iddia işleme ile etkileşimli bir yan panelde görüntülemek.

Sonunda, tarayıcınız içinde sorunsuz çalışan uçtan uca bir doğruluk kontrol aracı elde edeceksiniz. Kullanıcılar, herhangi bir web sayfasında metin seçip, destekleyici kaynaklarla birlikte anında doğrulama sonuçları alabilir; tüm bunlar API kimlik bilgilerinin sunucu tarafında güvende tutulmasıyla gerçekleşir.

Ayrıca ayrı bir yazıda Perplexity’nin Comet Tarayıcısı rehberimize da göz atabilirsiniz. 

Perplexity Search API Nedir?

Perplexity Search API, hızlı ve ayrıntılı web arama sonuçları almak için geliştirici dostu bir yoldur. 

Yalnızca tam sayfalar döndüren geleneksel arama API’lerinden farklı olarak, Perplexity sorgunuzla eşleşen tam cümleleri ve paragrafları içeren sıralanmış parçalar ve belge-altı bölümler sunar. Bu arama API’sinin bazı temel özellikleri şunlardır:

  • Maliyet etkin: Search API, 1.000 istek için 5 ABD doları olarak fiyatlandırılır ve token bazlı faturalama yoktur; bu da sık veya zamanlanmış kontroller çalıştıran uygulamalar için idealdir.
  • Yüksek kaliteli erişim: Leksik ve anlamsal sıralamayı çok-aşamalı yeniden sıralamayla birleştirir; böylece sürekli yenilenen devasa bir indeksten (~günde 200M sorgu) oldukça alakalı, güncel sonuçlar elde edersiniz.
  • Düşük gecikme: Perplexity arama API’si sorguları hızlı döndürür; böylece aynı anda birden fazla iddia doğrularken bile arayüzünüz duyarlı kalır.

Uzantımız için bu, herhangi bir cümleyi sorgu olarak geçirip, başlıklar, URL’ler ve bağlam parçalarıyla birlikte güvenilir destekleyici veya çelişen kanıtlar alabileceğimiz anlamına gelir; gerçek zamanlı doğruluk kontrolü için idealdir.

Perplexity Search API Projesi: Tarayıcı İddia Doğrulayıcı Uzantısı

Bu bölümde, Perplexity Search API’yi kullanarak olgusal iddiaları anında doğrulayan bir tarayıcı iddia doğrulayıcı uzantısı oluşturacağız. Bu proje şunları birleştirir:

  • Seçilen metni Perplexity Search API’ye gönderen ve sonuçları işleyen hafif bir Node.js backend
  • Doğrulama ayrıntılarını göstermek için yan panel arayüzüne sahip bir Chrome uzantısı
  • Herhangi bir web sayfasından kullanıcı tarafından seçilen metni yakalayan bir içerik komut dosyası

Projenin sonunda depo yapınız şu şekilde görünmelidir:

ClaimChecker/
├── server/
│   ├── server.js
│   ├── package.json
│   └── .env
├── extension/
│   ├── manifest.json
│   ├── background.js
│   ├── content.js
│   ├── sidepanel.html
│   ├── sidepanel.js
│   ├── popup.html
│   └── popup.js
└── README.md

Haydi bu projenin her bir parçasını adım adım inceleyelim ve tarayıcı içinde güvenilir iddia doğrulaması sunmak için nasıl birlikte çalıştıklarını görelim.

Adım 1: Bağımlılıklar ve kütüphaneler 

Başlamadan önce, aşağıdaki ön koşulların sağlandığından emin olun:

  • Bir Perplexity hesabı. Kaydolduktan veya oturum açtıktan sonra, API grubunuza ve ilgili ayarlara erişmek için sol kenar çubuğundan API bölümünü genişletin.

  • API Portal’ınızdaki API Billing sekmesine gidin ve ödeme yönteminiz ekleyin. Bu demo için yaklaşık 10 ABD doları kredi, istekleri karşılamak için yeterlidir.
  • Node.js sürüm 18 veya üzeri.
  • Chromium tabanlı tarayıcı (Google Chrome veya Microsoft Edge).

Ardından, backend için gerekli bağımlılıkları yükleyin:

cd server
npm install express cors dotenv

Sunucu dizininde bir .env dosyası oluşturun ve Perplexity API anahtarınızı ekleyin:

PERPLEXITY_API_KEY=your_api_key_here

Artık tüm bağımlılıkları kurduk.  Sırada backend sunucumuzu ve uzantımızı inşa etmek var.

Adım 2: Backend’i oluşturun

server.js dosyası, Express kullanarak hafif bir Node.js backend’i kurar. Bu backend, tarayıcı uzantısı ile Perplexity Search API arasında köprü görevi görür. Kullanıcı tarafından seçilen veya girilen iddiaları alır, Perplexity’den ilgili kanıtları sorgular ve uzantının arayüzünde görüntülenmeye hazır, temizlenmiş ve yapılandırılmış bir yanıt döndürür.

const express = require('express');
const cors = require('cors');
require('dotenv').config();
const app = express();
const PORT = 3000;
app.use(cors());
app.use(express.json());
function extractDomain(url) {
  try {
    const urlObj = new URL(url);
    return urlObj.hostname.replace('www.', '');
  } catch {
    return url;
  }
}
app.post('/check-claim', async (req, res) => {
  try {
    const { claim } = req.body;
    if (!claim) {
      return res.status(400).json({ error: 'Claim is required' });
    }
    if (!process.env.PERPLEXITY_API_KEY) {
      return res.status(500).json({ 
        error: 'Perplexity API key not configured',
        sources: []
      });
    }
    const response = await fetch('https://api.perplexity.ai/search', {
      method: 'POST',
      headers: {
        'Authorization': Bearer ${process.env.PERPLEXITY_API_KEY},
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        query: claim,
        max_results: 20,
        max_tokens_per_page: 1024
      })
    });
    if (!response.ok) {
      throw new Error(Perplexity API error: ${response.status});
    }
    const data = await response.json();
    const searchResults = data.results || [];   
    if (searchResults.length === 0) {
      return res.json({
        claim,
        sources: [],
        totalSources: 0,
        explanation: 'No relevant sources found to verify this claim.',
        summary: 'No sources found for fact-checking.'
      });
    }
    const sortedResults = searchResults.sort((a, b) => {
      const dateA = new Date(a.last_updated || a.date || 0);
      const dateB = new Date(b.last_updated || b.date || 0);
      return dateB - dateA;
    });
    const sources = sortedResults.slice(0, 3).map((result) => ({
      url: result.url,
      title: result.title,
      snippet: result.snippet || '',
      domain: extractDomain(result.url),
      date: result.date || null,
      lastUpdated: result.last_updated || null
    }));
    const result = {
      claim,
      sources,
      totalSources: searchResults.length,
      explanation: ${searchResults.length} sources found, showing top 3.,
      summary: Found ${searchResults.length} relevant sources for fact-checking.
    };
    res.json(result);
  } catch (error) {
    res.status(500).json({ 
      error: error.message,
      sources: []
    });
  }
});
app.get('/health', (req, res) => {
  res.json({ status: 'ok' });
});
app.listen(PORT, () => {
  console.log(Server running on http://localhost:${PORT});
  console.log('Make sure PERPLEXITY_API_KEY is set in .env file');
});

Yukarıdaki kod, Claim Checker uzantısı için güvenlik ve mantık katmanıdır ve şu işlevleri yerine getirir:

  • Güvenli ve mantıklı backend: POST /check-claim rotası üzerinden bir iddia alır, girdiyi doğrular ve sunucuda saklanan API anahtarını kullanarak Perplexity Search API’yi çağırır.
  • İşleme ve yapılandırma: Sunucu, ilgili web kaynakları ve alıntıların bir listesini alır, last_updated ve tarih alanlarını kullanarak yeniliğe göre sıralar, ilk üçünü seçer ve uzantı için temiz bir JSON yanıtı döndürür.
  • Anahtar güvenliği: Bu backend’i çalıştırmak, API anahtarınızı tarayıcıdan gizli tutar ve veriler kullanıcıya ulaşmadan önce sıralama, filtreleme veya önbellekleme gibi ek mantıklar eklemenize olanak tanır.
  • Genişletilebilir tasarım: Bu yaklaşım, uzantıyı daha güvenli ve gelişmiş puanlama, çok-kaynak birleştirme veya analitik gibi gelecekteki iyileştirmeler için kolayca genişletilebilir hale getirir.

Artık sunucumuz hazır. Şimdi bir Chrome uzantısı oluşturalım.

Adım 3: Chrome uzantısını oluşturma

Bu adımda, Claim Checker’ın kullanıcı deneyimini sağlayan Chrome uzantısını oluşturacağız. Uzantı, herhangi bir web sayfasından iddiaları yakalamaktan, bunları backend’imize göndermekten ve doğrulama sonuçlarını sezgisel bir arayüzde görüntülemekten sorumludur.

Her dosyaya dalmadan önce, bu temel bileşenlerin ne yaptığına bakalım:

  • background.js: Bu betik bir servis çalışanı olarak çalışır ve kullanıcı eylemlerini dinlemek, yan paneli açmak ve iddiaları doğrulama için güvenle backend’e göndermek gibi arka plan görevlerini yönetir.
  • content.js:  Bu içerik betiği, etkin web sayfasının içinde çalışır. Görünür makale metnini çıkarır, olası olgusal iddiaları belirler ve kullanıcının neyin kontrol edildiğini görebilmesi için bunları dinamik olarak vurgular.
  • sidepanel.js: Çıkarılan iddiaları ve doğrulama durumlarını gösteren etkileşimli yan panel arayüzünü kontrol eder. Sonuçları gerçek zamanlı günceller ve sayfadaki vurgularla ilişkilendirir.
  • popup.js: Kullanıcı uzantı simgesine tıkladığında açılan hızlı erişim açılır pencereyi çalıştırır. Yan paneli açmadan iddiaları kontrol etmenin hafif bir yolunu sunar.

Chrome uzantısı için birkaç ek yardımcı dosyayı GitHub deposunda bulabilirsiniz.

Adım 3.1: Arka plan betiği ve olay işleme 

Chrome uzantısı kurulumumuz üzerine inşa ederek, background.js dosyası uzantının tepkisel kalmasını sağlayan merkezi denetleyicidir. Kullanıcı eylemlerini (uzantı simgesine tıklama gibi) dinler, gerektiğinde yan paneli açar ve iddiaları doğrulama için backend’e iletir.

const SERVER_URL = 'http://localhost:3000';
chrome.action.onClicked.addListener(async (tab) => {
  try {
    await chrome.sidePanel.open({ tabId: tab.id });
  } catch (error) {
    console.error('Error opening side panel:', error);
    chrome.action.setPopup({ popup: 'popup.html' });
  }
});
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'checkClaim') {
    checkClaim(request.claim)
      .then(result => sendResponse({ success: true, result }))
      .catch(error => sendResponse({ success: false, error: error.message }));
    return true; 
  }
});
async function checkClaim(claim) {
  try {
    const response = await fetch(${SERVER_URL}/check-claim, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ claim })
    });
    if (!response.ok) {
      throw new Error(Server error: ${response.status});
    }
    return await response.json();
  } catch (error) {
    console.error('Error checking claim:', error);
    throw error;
  }
}

Yukarıdaki kod, aşağıdakileri içerecek şekilde uzantının tepkiselliğine odaklanır:

  • Merkezi olay merkezi: Arka plan betiği, chrome.action.onClicked ve chrome.runtime.onMessage olmak üzere iki ana dinleyici kaydeder. İlki, kullanıcı uzantı simgesine tıkladığında tepki verir ve yan paneli açmaya çalışır. İkincisi, içerik betiğinden veya arayüz bileşenlerinden gelen checkClaim gibi özel mesajları dinler.
  • Güvenli API işleme: checkClaim() işlevi, seçilen iddiayı http://localhost:3000/check-claim adresindeki backend’e göndermek için fetch API’sini kullanır; API anahtarı sunucu tarafında saklandığından, tarayıcı hiçbir zaman onu açığa çıkarmaz ve hassas kimlik bilgileri güvende kalır.
  • Hata dayanıklılığı: Her iki olay dinleyicisi de çalışma zamanı hatalarını ele almak için try/catch blokları içerir. Yan panel açılamazsa, betik açılır pencereyi ayarlamaya geri döner. API çağrıları başarısız olduğunda, kullanıcıların sessiz başarısızlıklar yaşamaması için hataları günlüğe kaydeder ve arayüze yapılandırılmış hata mesajları döndürür.

Arka plan betiği iletişimi ve güvenli API çağrılarını hallederken, sıradaki adım doğrudan web sayfasıyla etkileşim kurmaktır.

Adım 3.2: İddiaları çıkarma ve vurgulama

content.js dosyası doğrudan web sayfasının içinde çalışır ve makale metnini çıkarmaktan, olası olgusal iddiaları belirlemekten ve kullanıcı için bunları görsel olarak vurgulamaktan sorumludur. İlgili metni yakalayarak ve kullanıcı etkileşimini mümkün kılarak, sayfa içeriği ile uzantının arka plan mantığı arasında köprü kurar.

function extractArticleText() {
    const selectors = [
      'article',
      '[role="article"]',
      '.article-content',
      '.post-content',
      '.entry-content',
      'main'
    ];  
    for (const selector of selectors) {
      const element = document.querySelector(selector);
      if (element) {
        return element.innerText;
      }
    }  
    return document.body.innerText;
  }  
  function extractClaims(text) {
    const sentenceMatches = text.match(/[^.!?]*[.!?]+/g) || [];
    const sentences = sentenceMatches
      .map(s => s.trim())
      .filter(s => s.length > 20 && s.length < 1000);
    const claimKeywords = [
      /\d+%/, 
      /\d+\s+(million|billion|thousand|trillion)/i, 
      /according to/i,
      /study shows/i,
      /research found/i,
      /scientists/i,
      /experts/i,
      /will|would|could|should/i,
      /increase|decrease|rise|fall/i,
      /more than|less than|at least/i,
      /proven|showed|demonstrated/i,
      /investment|invest|create|generate/i,  
      /jobs|employment|economy/i,  
      /ripple effect|impact|effect/i  
    ];  
    const claims = [];    
    for (const sentence of sentences) {
      const hasClaimKeyword = claimKeywords.some(regex => regex.test(sentence));      
      const isValidLength = sentence.length > 30 && sentence.length < 800;
      const notNavigation = !/(click here|read more|subscribe|sign up|menu|home|cookie|privacy)/i.test(sentence);
      const hasCompleteThought = /[.!?]$/.test(sentence) || sentence.length > 50;      
      if (hasClaimKeyword && isValidLength && notNavigation && hasCompleteThought) {
        let cleanSentence = sentence
          .replace(/\s+/g, ' ')  
          .replace(/^\s*[-•]\s*/, '') 
          .trim();        
        if (cleanSentence.length > 30) {
          claims.push(cleanSentence);
        }
      }
    }  
    return claims.slice(0, 10);
  }  
let highlightedElements = new Map();
let claimIdCounter = 0;
function highlightTextOnPage(claim, claimId) {
  const text = document.body.innerText;
  const claimWords = claim.toLowerCase().split(/\s+/).filter(word => word.length > 3);  
  if (claimWords.length < 3) return null;
  const sentences = text.split(/[.!?]+/).map(s => s.trim()).filter(s => s.length > 20);
  let bestMatch = null;
  let bestScore = 0;  
  for (const sentence of sentences) {
    const sentenceWords = sentence.toLowerCase().split(/\s+/);
    let score = 0;    
    for (const word of claimWords) {
      if (sentenceWords.some(sw => sw.includes(word) || word.includes(sw))) {
        score++;
      }
    }    
    if (score > bestScore && score >= Math.min(3, claimWords.length * 0.5)) {
      bestScore = score;
      bestMatch = sentence;
    }
  }  
  if (!bestMatch) return null;  
  const walker = document.createTreeWalker(
    document.body,
    NodeFilter.SHOW_TEXT,
    null,
    false
  );  
  let node;
  while (node = walker.nextNode()) {
    if (node.textContent.includes(bestMatch)) {
      const parent = node.parentElement;
      if (parent && !parent.classList.contains('claim-highlight')) {
        const highlight = document.createElement('span');
        highlight.className = 'claim-highlight';
        highlight.id = claim-${claimId};
        highlight.style.cssText = 
          background: linear-gradient(120deg, #fbbf24 0%, #f59e0b 100%);
          
          border-radius: 3px;
          box-shadow: 0 1px 3px rgba(0,0,0,0.2);
          cursor: pointer;
          transition: all 0.2s ease;
          position: relative;
        ;        
        const textContent = node.textContent;
        const index = textContent.indexOf(bestMatch);
        if (index !== -1) {
          const before = textContent.substring(0, index);
          const after = textContent.substring(index + bestMatch.length);         
          const beforeNode = document.createTextNode(before);
          const afterNode = document.createTextNode(after);          
          highlight.textContent = bestMatch;
          node.parentNode.insertBefore(beforeNode, node);
          node.parentNode.insertBefore(highlight, node);
          node.parentNode.insertBefore(afterNode, node);
          node.parentNode.removeChild(node);
          highlight.addEventListener('mouseenter', () => {
            highlight.style.transform = 'scale(1.05)';
            highlight.style.boxShadow = '0 2px 8px rgba(0,0,0,0.3)';
          });    
          highlight.addEventListener('mouseleave', () => {
            highlight.style.transform = 'scale(1)';
            highlight.style.boxShadow = '0 1px 3px rgba(0,0,0,0.2)';
          });   
          highlight.addEventListener('click', () => {
            chrome.runtime.sendMessage({
              action: 'highlightClaimInPanel',
              claimId: claimId
            });        
            highlight.style.background = 'linear-gradient(120deg, #10b981 0%, #059669 100%)';
            setTimeout(() => {
              highlight.style.background = 'linear-gradient(120deg, #fbbf24 0%, #f59e0b 100%)';
            }, 1000);
          });     
          highlightedElements.set(claimId, highlight);
          return claimId;
        }
      }
      break;
    }
  }  
  return null;
}
function removeHighlights() {
  highlightedElements.forEach((element, claimId) => {
    if (element && element.parentNode) {
      const parent = element.parentNode;
      const text = element.textContent;
      const textNode = document.createTextNode(text);
      parent.insertBefore(textNode, element);
      parent.removeChild(element);
    }
  });
  highlightedElements.clear();
}
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'extractClaims') {
    const text = extractArticleText();
    const claims = extractClaims(text);
    sendResponse({ claims, url: window.location.href });
  } else if (request.action === 'highlightClaim') {
    const { claim, claimId } = request;
    const highlightId = highlightTextOnPage(claim, claimId);
    sendResponse({ success: true, highlightId });
  } else if (request.action === 'removeHighlights') {
    removeHighlights();
    sendResponse({ success: true });
  }
  return true;
});

content.js betiğinin önemli bölümlerini, anlamlı metni nasıl çıkardığını, olası iddiaları nasıl tespit ettiğini ve vurgulanan içeriği uzantının arayüzüne nasıl bağladığını anlamak için parçalara ayıralım:

  • Makale metni çıkarma: extractArticleText() işlevi, ana okunabilir içeriği almak için yaygın makale kapsayıcılarını tarar. Bu, uzantının gezinme menüleri veya reklamlardan ziyade anlamlı metne odaklanmasına yardımcı olur.
  • İddia tespit mantığı: extractClaims() işlevi, metni cümlelere böler, uzunluk ve yapı için filtreler uygular ve olgusal iddia olma olasılığı yüksek ifadeleri tespit etmek için regex tabanlı anahtar kelimeler kullanır.
  • Dinamik vurgulama: highlightTextOnPage() işlevi, tespit edilen iddiayı DOM içinde bulur ve biçimlendirilmiş bir <span> içine sarar. Kullanıcıların sayfadaki iddialarla yan paneldeki doğrulama sonuçlarını görsel olarak ilişkilendirebilmesi için üzerine gelme ve tıklama etkileşimleri ekler.
  • Mesaj işleme: chrome.runtime.onMessage dinleyicisi, iddiaları çıkarma, vurguları ekleme ve gerektiğinde temizleme gibi eylemleri koordine eder. Bu, sayfa ile uzantıyı senkronize tutar ve sorunsuz bir kullanıcı etkileşimi sağlar.

İçerik betiği sayfada metin çıkarma ve iddia vurgulamayı ele aldığından, sıradaki adım bu iddiaları ve doğrulama sonuçlarını kullanıcıya sunmaktır. Burada sidepanel.js arayüzü devreye girer.

Adım 3.3: Yan panel arayüzü

sidepanel.js dosyası, kullanıcıların çıkarılan iddiaları ve doğrulama sonuçlarını görebileceği etkileşimli yan paneli çalıştırır. İçerik betiği ve backend yanıtlarını uzantı içinde temiz, kaydırılabilir bir arayüze bağlar.

let claimCounter = 0;
let currentHighlights = new Map();
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'highlightClaimInPanel') {
    const claimId = request.claimId;
    const claimElement = document.getElementById(sidepanel-${claimId});    
    if (claimElement) {
      claimElement.scrollIntoView({ 
        behavior: 'smooth', 
        block: 'center' 
      });      
      claimElement.classList.add('highlighted');      
      claimElement.style.animation = 'pulse 1s ease-in-out';     
      setTimeout(() => {
        claimElement.classList.remove('highlighted');
        claimElement.style.animation = '';
      }, 3000);
    }
  }
});
document.getElementById('check-btn').addEventListener('click', async () => {
    const button = document.getElementById('check-btn');
    const container = document.getElementById('claims-container');
    const status = document.getElementById('status');   
    button.disabled = true;
    container.innerHTML = '<div class="loading">Extracting claims...</div>';
    status.textContent = '';    
    try {
      const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
      await chrome.tabs.sendMessage(tab.id, { action: 'removeHighlights' });
    } catch (error) {
    }   
    try {
      const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
      const response = await chrome.tabs.sendMessage(tab.id, { action: 'extractClaims' });    
      if (!response || !response.claims || response.claims.length === 0) {
        container.innerHTML = '<div class="no-claims">No claims found on this page.</div>';
        button.disabled = false;
        return;
      }
      status.textContent = Found ${response.claims.length} claims. Checking...;
      container.innerHTML = '';      
      for (let i = 0; i < response.claims.length; i++) {
        const claim = response.claims[i];
        const claimId = claim-${++claimCounter};
        status.textContent = Checking claim ${i + 1} of ${response.claims.length}...;        
        try {
          await chrome.tabs.sendMessage(tab.id, { 
            action: 'highlightClaim', 
            claim, 
            claimId 
          });
        } catch (error) {
        }      
        const claimDiv = document.createElement('div');
        claimDiv.className = 'claim-item';
        claimDiv.id = sidepanel-${claimId};
        claimDiv.innerHTML = 
          <div class="claim-text">${escapeHtml(claim)}</div>
          <div class="loading">Verifying...</div>
        ;
        container.appendChild(claimDiv);
        chrome.runtime.sendMessage(
          { action: 'checkClaim', claim },
          (checkResponse) => {
            if (checkResponse.success) {
              const result = checkResponse.result;      
              const sourcesHtml = result.sources && result.sources.length > 0 ? 
                <div class="claim-sources">
                  ${result.sources.map(s => 
                    <div class="source-item">
                      <a href="${s.url}" target="_blank" class="source-link">
                        ${s.title || s.domain}
                      </a>
                      <div class="source-domain">
                        ${s.domain}
                        ${s.lastUpdated || s.date ?  • ${new Date(s.lastUpdated || s.date).toLocaleDateString()} : ''}
                      </div>
                      ${s.snippet ? <div class="source-snippet">${escapeHtml(s.snippet.substring(0, 150))}${s.snippet.length > 150 ? '...' : ''}</div> : ''}
                    </div>
                  ).join('')}
                </div> : 
                '<div class="claim-sources">No sources found</div>';             
              claimDiv.innerHTML = 
                <div class="claim-text">${escapeHtml(claim)}</div>
                ${sourcesHtml}
              ;          
              currentHighlights.set(claimId, claimDiv);
            } else {
              claimDiv.innerHTML = 
                <div class="claim-text">${escapeHtml(claim)}</div>
                <div class="error">Error: ${checkResponse.error}</div>
              ;
            }
          }
        );  
        if (i < response.claims.length - 1) {
          await new Promise(resolve => setTimeout(resolve, 500));
        }
      }  
      status.textContent = 'All claims checked! Click highlighted text on the page to jump to the corresponding claim details.';
      button.disabled = false; 
    } catch (error) {
      container.innerHTML = <div class="error">Error: ${error.message}</div>;
      button.disabled = false;
      status.textContent = '';
    }
  });  
  function escapeHtml(text) {
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
  }

İşte yan panelin tarayıcınızda nasıl çalıştığı:

  • Dinamik iddia listesi: Kullanıcı “Check Claims” düğmesine tıkladığında, betik etkin sekmeden iddiaları ister, eski vurguları temizler ve yer tutucu durumlarla bir iddia listesi oluşturur.
  • Gerçek zamanlı doğrulama güncellemeleri: Her iddia, checkClaim eylemiyle chrome.runtime.sendMessage kullanılarak arka plan betiğine gönderilir. Backend sonuç döndürdüğünde, arayüz kaynaklar, alan adları ve alıntılarla anında güncellenir.
  • Etkileşimli bağlantı: Betik, highlightClaimInPanel mesajlarını dinler; böylece sayfadaki vurgulu metne tıklamak, yan paneldeki ilgili iddia ayrıntılarına kaydırır. Bu, sayfa vurguları ile doğrulama verileri arasında akıcı bir gezinme döngüsü oluşturur.

Sırada, önceki adımlarda oluşturulan uzantıyı başlatmayı ve kullanmayı kolaylaştırmak için hızlı erişim açılır penceresi ekleyeceğiz.

Adım 3.4: Hızlı erişim açılır pencere arayüzü

popup.js, kullanıcı uzantı simgesine tıkladığında görünen hafif bir açılır pencere arayüzüdür. Yan panelden farklı olarak, bu açılır pencere mevcut sayfadan ayrılmadan veya tam etkileşimli paneli açmadan iddiaları kontrol etmenin daha hızlı, isteğe bağlı bir yolunu sağlar.

document.getElementById('check-btn').addEventListener('click', async () => {
    const button = document.getElementById('check-btn');
    const container = document.getElementById('claims-container');
    const status = document.getElementById('status');   
    button.disabled = true;
    container.innerHTML = '<div class="loading">Extracting claims...</div>';
    status.textContent = '';  
    try {
      const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });     
      const response = await chrome.tabs.sendMessage(tab.id, { action: 'extractClaims' });  
      if (!response || !response.claims || response.claims.length === 0) {
        container.innerHTML = '<div class="error">No claims found on this page.</div>';
        button.disabled = false;
        return;
      }   
      status.textContent = Found ${response.claims.length} claims. Checking...;
      container.innerHTML = '';
      for (let i = 0; i < response.claims.length; i++) {
        const claim = response.claims[i];
        status.textContent = Checking claim ${i + 1} of ${response.claims.length}...;   
        const claimDiv = document.createElement('div');
        claimDiv.className = 'claim-item';
        claimDiv.innerHTML = 
          <div class="claim-text">${escapeHtml(claim)}</div>
          <div class="loading">Verifying...</div>
        ;
        container.appendChild(claimDiv);      
        chrome.runtime.sendMessage(
          { action: 'checkClaim', claim },
          (checkResponse) => {
            if (checkResponse.success) {
              const result = checkResponse.result;
              const confidenceBar = result.confidence ? 
                <div class="confidence-bar">
                  <div class="confidence-fill" style="width: ${result.confidence}%"></div>
                  <span class="confidence-text">${result.confidence}% confidence</span>
                </div> : '';     
              const explanation = result.explanation ? 
                <div class="claim-explanation">${escapeHtml(result.explanation)}</div> : '';    
              const rebuttal = result.rebuttal ? 
                <div class="claim-rebuttal">
                  <strong>Counter-argument:</strong> ${escapeHtml(result.rebuttal)}
                </div> : '';  
              const sourcesHtml = result.sources && result.sources.length > 0 ? 
                <div class="claim-sources">
                  <strong>Sources (${result.sources.length}):</strong>
                  ${result.sources.map(s => 
                    <div class="source-item">
                      <a href="${s.url}" target="_blank" class="source-link">
                        ${s.domain || s.title}
                      </a>
                      ${s.snippet ? <div class="source-snippet">${escapeHtml(s.snippet)}</div> : ''}
                    </div>
                  ).join('')}
                </div> : 
                '<div class="claim-sources">No sources found</div>'; 
              claimDiv.innerHTML = 
                <div class="claim-text">${escapeHtml(claim)}</div>
                <div class="claim-verdict verdict-${result.verdict.toLowerCase()}">
                  ${result.verdict}
                </div>
                ${confidenceBar}
                ${explanation}
                ${rebuttal}
                ${sourcesHtml}
              ;
            } else {
              claimDiv.innerHTML = 
                <div class="claim-text">${escapeHtml(claim)}</div>
                <div class="error">Error: ${checkResponse.error}</div>
              ;
            }
          }
        );       
        if (i < response.claims.length - 1) {
          await new Promise(resolve => setTimeout(resolve, 500));
        }
      }  
      status.textContent = 'All claims checked!';
      button.disabled = false;      
    } catch (error) {
      container.innerHTML = <div class="error">Error: ${error.message}</div>;
      button.disabled = false;
      status.textContent = '';
    }
  }); 
  function escapeHtml(text) {
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
  }

İşte popup.js betiğinin, uzantı araç çubuğundan doğrudan isteğe bağlı bir iddia doğrulayıcıyı nasıl çalıştırdığı:

  • Sayfa taraması ve iddia gösterimi: Kullanıcı “Check” düğmesine tıkladığında, açılır pencere içerik betiği aracılığıyla etkin sekmeden metin ister, olası iddiaları hızla çıkarır ve bunları açılır pencerede listeler.
  • Backend doğrulaması: Her iddia, /check-claim API rotasını çağırmak üzere arka plan betiğine gönderilir. Açılır pencere, sunucudan dönen hüküm, güven çubukları, açıklamalar, karşı argümanlar ve kaynak bağlantılarıyla gerçek zamanlı güncellenir.

Artık uzantımız test edilmeye hazır. 

Not: Bu projeye ait tüm kod dosyalarını GitHub deposunda bulabilirsiniz.

Adım 4: Uygulamayı çalıştırma

Backend sunucusunu aşağıdaki komutla başlatın:

npm start

Sunucu çalışmaya başladıktan sonra Chrome uzantısını yükleyin:

  • Chrome’u açın ve “Extensions”a tıklayın (arama çubuğunun sağ tarafında).
  • “Developer mode”u açın ve “Load unpacked”e tıklayın (sol üst). 
  • Ardından, tüm dosyaları içeren uzantılar klasörümüzü yükleyin ve onayı bekleyin.

  • Chrome uzantının eklendiğini onayladıktan sonra, herhangi bir web sayfasında Claim Checker simgesine tıklayın, doğrulamak istediğiniz metni seçin ve iddia kontrolünü çalıştırın.

Sonuçlara bakalım:

Sonuç

Artık tamamen Perplexity’nin Search API’siyle çalışan (LLM çağrısı gerektirmeyen) hızlı ve uygun maliyetli bir iddia doğrulayıcınız var. Bu demo, taze ve güvenilir arama sonuçları, parça düzeyinde alaka, düşük gecikme ve öngörülebilir fiyatlandırma gibi API’nin temel güçlü yanlarını öne çıkarır.

Buradan, gelişmiş filtreler ekleyerek, otomatik kontrolleri zamanlayarak veya bir RAG hattı için ön filtre olarak entegre ederek projeyi genişletebilirsiniz. Daha ileri gitmek için Perplexity API dokümanlarını ve hızlı başlangıç rehberlerini keşfederek daha gelişmiş arama ve filtreleme yeteneklerinin kilidini açın.


Aashi Dutt's photo
Author
Aashi Dutt
LinkedIn
Twitter

ML (Üretken Yapay Zekâ) alanında Google Developers Uzmanıyım, Kaggle 3x Expert unvanına sahibim ve 3+ yıllık teknoloji deneyimiyle Women Techmakers Elçisiyim. 2020'de bir sağlık teknolojileri girişiminin kurucu ortağı oldum ve Georgia Tech'te makine öğrenmesi alanında uzmanlaşarak bilgisayar bilimleri yüksek lisansı yapıyorum.

Konular
Yapay Zeka
Yapay Zekâ Aracıları

En İyi DataCamp Kursları

Kurs

AI Agent'lere Giriş

1 sa 30 dk
145.4K
AI ajanlarının temellerini, bileşenlerini ve gerçek hayattaki kullanımlarını öğrenin — kodlama gerekmez.
Ayrıntıları GörüntüleRight Arrow
Kursa Başla
Devamını GörRight Arrow
İlgili

blog

Hızlı Sevkiyat İçin Pratik Vibe Kodlama Teknoloji Yığını

Ön uç, arka uç, veritabanları, kimlik doğrulama, depolama, e-posta, test, dağıtım ve izleme için en iyi araçları keşfedin.
Abid Ali Awan's photo

Abid Ali Awan

14 dk.

blog

2026’da En Popüler 40 Yazılım Mühendisi Mülakat Sorusu

Algoritmalar, sistem tasarımı ve davranışsal senaryoları kapsayan bu temel sorularla teknik mülakat sürecine hakim olun. Uzman cevapları, kod örnekleri ve kanıtlanmış hazırlık stratejileri edinin.
Dario Radečić's photo

Dario Radečić

15 dk.

Eğitim

Python'da Listeyi String'e Nasıl Dönüştürürsünüz

Bu hızlı eğitimde, Python'da bir listeyi string'e nasıl dönüştüreceğinizi öğrenin.
Adel Nehme's photo

Adel Nehme

Eğitim

.gitignore Nasıl Kullanılır: Örneklerle Pratik Bir Giriş

Git deponuzu temiz tutmak için .gitignore’u nasıl kullanacağınızı öğrenin. Bu eğitim; temelleri, yaygın kullanım durumlarını ve başlamanıza yardımcı olacak pratik örnekleri kapsar!
Kurtis Pykes 's photo

Kurtis Pykes

8 dk.

Devamını GörDevamını Gör