/*
Prerequisites
Acknowledge Google’s Generative AI Prohibited Uses Policy.
Download Chrome Dev channel (or Canary channel), and confirm that your version is equal or newer than 128.0.6545.0.
Check that your device meets the requirements.
Don’t skip this step, in particular make sure that you have at least 22 GB of free storage space.
If after the download the available storage space falls below 10 GB, the model will be deleted again. Note that some operating systems may report the actual free disk space differently, for example, by including or not disk space that's occupied by the trash bin. On macOS, use Disk Utility to get the representative free disk space.
Enable Gemini Nano and the Prompt API
Follow these steps to enable Gemini Nano and the Prompt API flags for local experimentation:
Open a new tab in Chrome, go to chrome://flags/#optimization-guide-on-device-model
Select Enabled BypassPerfRequirement
This bypass performance checks which might get in the way of having Gemini Nano downloaded on your device.
Go to chrome://flags/#prompt-api-for-gemini-nano
Select Enabled
Relaunch Chrome.
Confirm availability of Gemini Nano
Open DevTools and send (await ai.languageModel.capabilities()).available; in the console.
If this returns “readily”, then you are all set.
If this fails, continue as follows:
📣🆕Force Chrome to recognize that you want to use this API. To do so, open DevTools and send await ai.languageModel.create(); in the console. This will likely fail but it’s intended.
Relaunch Chrome.
Open a new tab in Chrome, go to chrome://components
Confirm that Gemini Nano is either available or is being downloaded
You'll want to see the Optimization Guide On Device Model present with a version greater or equal to 2024.5.21.1031.
If there is no version listed, click on Check for update to force the download.
Once the model has downloaded and has reached a version greater than shown above, open DevTools and send (await ai.languageModel.capabilities()).available; in the console. If this returns “readily”, then you are all set.
Otherwise, relaunch, wait for a little while, and try again from step 1.
*/
// ContentAnalyzer class to handle webpage content extraction and analysis
class ContentAnalyzer {
constructor(maxRetries=3, retryDelay=1000) {
this.readabilityLoaded = false;
this.maxRetries = maxRetries;
this.retryDelay = retryDelay;
}
// Utility function to delay execution
async delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Retry wrapper for async functions
async withRetry(operation, context='') {
let lastError;
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error;
console.warn(`Attempt ${attempt}/${this.maxRetries} failed ${context}:`, error.message);
if (attempt < this.maxRetries) {
await this.delay(this.retryDelay * attempt);
// Exponential backoff
}
}
}
throw new Error(`Failed after ${this.maxRetries} attempts ${context}: ${lastError.message}`);
}
// Load the readability script dynamically if not already included
async loadReadability() {
if (!this.readabilityLoaded && !window.Readability) {
return this.withRetry(async () => {
const script = document.createElement("script");
script.src = "https://cdn.jsdelivr.net/npm/@mozilla/readability@0.5.0/Readability.min.js";
script.type = "text/javascript";
document.head.appendChild(script);
await new Promise( (resolve, reject) => {
script.onload = resolve;
script.onerror = () => reject(new Error('Failed to load Readability script'));
}
);
this.readabilityLoaded = true;
}
, 'while loading Readability script');
}
}
// Extract readable content from the current page
async extractContent() {
await this.loadReadability();
return this.withRetry(async () => {
const doc = new DOMParser().parseFromString(document.documentElement.outerHTML, "text/html");
const reader = new Readability(doc);
const article = reader.parse();
if (!article) {
throw new Error('No readable content found');
}
return article.textContent;
}
, 'while extracting content');
}
// Extract specified number of sentences from the text
getFirstSentences(text, sentenceCount=4) {
if (!text || typeof text !== 'string') {
throw new Error('Invalid input text');
}
const sentences = text.match(/[^.!?]+[.!?]+/g);
if (!sentences || sentences.length === 0) {
return text;
}
return sentences.slice(0, sentenceCount).join(" ").trim();
}
// Normalize entity text
normalizeEntity(entity) {
return entity // Remove parenthetical content
.replace(/\s*\([^)]*\)/g, '')// Replace multiple spaces with single space
.replace(/\s+/g, ' ')// Trim whitespace
.trim()// Title case conversion while preserving common acronyms
.split(' ').map(word => {
// Preserve common acronyms (all uppercase words)
if (word.toUpperCase() === word && word.length > 1) {
return word;
}
// Title case for other words
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
}
).join(' ');
}
// Analyze entities using Nano LLM
async analyzeEntities(text) {
if (!text || typeof text !== 'string') {
throw new Error('Invalid input text for entity analysis');
}
return this.withRetry(async () => {
const lm = await window.ai.languageModel.create();
const prompt = `Extract ONLY the most significant and well-known entities (people, places, organizations, etc.) from the following text.
Focus on entities that would be widely recognized or notable (e.g., "Barack Obama" rather than "John Smith", "Microsoft" rather than a local business).
Return ONLY the entities as a list with one entity per line, like this example:
Barack Obama
Microsoft Corporation
New York City
United Nations
Do not include generic terms, descriptive phrases, or lesser-known entities. Each entity should be a specific, well-known proper noun.
Here's the text to analyze:
${text}
Entities (one per line):`;
const response = await lm.prompt(prompt);
// Normalize and deduplicate entities
const normalizedEntities = [...new Set(response.trim().split('\n').map(entity => this.normalizeEntity(entity)).filter(entity => // Remove empty strings and single-word generic terms
entity.length > 0 && // Filter out entities that are too short or too long
entity.length >= 2 && entity.length <= 100 && // Filter out entities that are just numbers
!/^\d+$/.test(entity)))];
if (normalizedEntities.length === 0) {
throw new Error('No valid entities found in the response');
}
return normalizedEntities;
}
, 'while analyzing entities');
}
// Main analysis function that can be called with custom parameters
async analyzePageContent({sentenceCount=10}={}) {
try {
const content = await this.extractContent();
const firstSentences = this.getFirstSentences(content, sentenceCount);
const entities = await this.analyzeEntities(firstSentences);
return {
summary: firstSentences,
entities: entities
};
} catch (error) {
console.error("Error in content analysis:", error);
throw error;
}
}
}
// Create and export the analyzer instance
const analyzer = new ContentAnalyzer();
// Example usage:
try {
console.log('Awaiting Result....')
const result = await analyzer.analyzePageContent({
sentenceCount: 30
});
console.log('Analysis Result:', result);
} catch (error) {
console.error('Analysis failed:', error);
}