Last chance! 50% off DataCamp Premium
Sale ends in
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:
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, 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:
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.
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:
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.
Başlamadan önce, aşağıdaki ön koşulların sağlandığından emin olun:

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.
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:
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.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.Artık sunucumuz hazır. Şimdi bir Chrome uzantısı oluşturalım.
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.
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:
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.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.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.
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:
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.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.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.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.
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ığı:
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.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.
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ığı:
/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.
Backend sunucusunu aşağıdaki komutla başlatın:
npm start
Sunucu çalışmaya başladıktan sonra Chrome uzantısını yükleyin:

Sonuçlara bakalım:

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.
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.
En İyi DataCamp Kursları
Kurs
Kurs
Kurs

blog
Abid Ali Awan
14 dk.
blog
Dario Radečić
15 dk.
Eğitim
Adel Nehme
Eğitim
Kurtis Pykes
8 dk.