initial commit

This commit is contained in:
2025-07-06 01:29:40 +00:00
commit d27ddca247
16 changed files with 4525 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
dist/
node_modules/

1
.tool-versions Normal file
View File

@ -0,0 +1 @@
nodejs 24.3.0

124
background.js Normal file
View File

@ -0,0 +1,124 @@
import { pipeline, env } from './transformers/transformers.min.js';
env.allowLocalModels = true;
env.backends.onnx.wasm.wasmPaths = {
'default': './transformers/',
'threaded': './transformers/',
'simd': './transformers/',
'simd-threaded': './transformers/',
};
env.allowRemoteModels = false;
let classifier = null;
let llmInitializationPromise = null;
async function initializeLLM() {
if (classifier) {
return;
}
if (llmInitializationPromise) {
return llmInitializationPromise;
}
llmInitializationPromise = (async () => {
try {
classifier = await pipeline(
'zero-shot-classification',
'nli-deberta-v3-small',
{
local_files_only: true,
model_file_name: "model.onnx",
}
);
llmInitializationPromise = null;
} catch (error) {
classifier = null;
llmInitializationPromise = null;
throw error;
}
})();
return llmInitializationPromise;
}
initializeLLM();
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
(async () => {
if (!classifier) {
try {
await initializeLLM();
} catch (error) {
sendResponse({ type: "LLM_ERROR", error: "LLM initialization failed: " + error.message });
return;
}
}
try {
switch (message.type) {
case 'ANALYZE_SENTIMENT':
const headlineForSentiment = message.payload.headline;
const sentimentLabels = ['negative', 'positive', 'neutral', 'depressing', 'worrying'];
const sentimentOutput = await classifier(headlineForSentiment, sentimentLabels);
let detectedSentiment = 'neutral';
if (sentimentOutput && sentimentOutput.scores && sentimentOutput.labels) {
const sortedSentimentResults = sentimentOutput.scores.map((score, index) => ({
label: sentimentOutput.labels[index],
score: score
})).sort((a, b) => b.score - a.score);
if (sortedSentimentResults.length > 0 && sortedSentimentResults[0].score > 0.7) {
detectedSentiment = sortedSentimentResults[0].label;
} else if (sortedSentimentResults.length > 0) {
const topLabel = sortedSentimentResults[0].label;
if (['negative', 'depressing', 'worrying'].includes(topLabel)) {
detectedSentiment = topLabel;
} else {
}
}
}
sendResponse({ type: "ANALYSIS_RESULT", result: { sentiment: detectedSentiment } });
break;
case 'ANALYZE_HEADLINE_CATEGORY':
const headline = message.payload.headline;
const selectedIssues = message.payload.selectedIssues || [];
const candidateLabels = ['gun-control', 'gun-rights', 'privacy', 'immigration', 'climate-change', 'healthcare', 'racial-justice', 'lgbtq-rights', 'economic-inequality', 'reproductive-rights', 'education-reform', 'criminal-justice', 'voting-rights', 'campaign-finance', 'foreign-policy', 'national-security', 'free-speech', 'net-neutrality', 'labor-rights', 'affordable-housing', 'environmental-protection', 'space-exploration', 'drug-policy-reform', 'veterans-affairs', 'senior-citizen-rights'];
const analysisOutput = await classifier(headline, candidateLabels);
let categories = [];
if (analysisOutput && analysisOutput.scores && analysisOutput.labels) {
const sortedResults = analysisOutput.scores.map((score, index) => ({
label: analysisOutput.labels[index],
score: score
})).sort((a, b) => b.score - a.score);
categories = sortedResults
.filter(item => item.score > 0.5 && selectedIssues.includes(item.label))
.map(item => item.label);
if (categories.length === 0 && sortedResults.length > 0) {
const topLabel = sortedResults[0].label;
if (selectedIssues.includes(topLabel)) {
categories.push(topLabel);
} else {
}
}
}
sendResponse({ type: "ANALYSIS_RESULT", result: { categories: categories } });
break;
default:
sendResponse({ type: "UNKNOWN_MESSAGE" });
break;
}
} catch (error) {
sendResponse({ type: "LLM_ERROR", error: "Processing error: " + error.message });
}
})();
return true;
});

278
content.js Normal file
View File

@ -0,0 +1,278 @@
function createPraxisBanner(content) {
const banner = document.createElement('div');
banner.id = 'praxis-banner';
banner.innerHTML = content;
Object.assign(banner.style, {
position: 'sticky',
top: '0',
left: '0',
width: '100%',
padding: '12px',
backgroundColor: '#374151',
color: '#f3f4f6',
textAlign: 'center',
zIndex: '999999',
fontSize: '16px',
boxSizing: 'border-box',
borderBottom: '1px solid #4b5563'
});
document.body.prepend(banner);
return banner;
}
(async () => {
const headlineElement = document.querySelector("h1");
const headlineText = headlineElement ? headlineElement.textContent : null;
if (!headlineText) {
return;
}
const ISSUES = [
{ value: "gun-control", text: "Gun Control" },
{ value: "gun-rights", text: "Gun Rights" },
{ value: "privacy", text: "Electronic Privacy" },
{ value: "immigration", text: "Immigration" },
{ value: "climate-change", text: "Climate Change" },
{ value: "healthcare", text: "Healthcare" },
{ value: "racial-justice", text: "Racial Justice" },
{ value: "lgbtq-rights", text: "LGBTQ+ Rights" },
{ value: "economic-inequality", text: "Economic Inequality" },
{ value: "reproductive-rights", text: "Reproductive Rights" },
{ value: "education-reform", text: "Education Reform" },
{ value: "criminal-justice", text: "Criminal Justice" },
{ value: "voting-rights", text: "Voting Rights" },
{ value: "campaign-finance", text: "Campaign Finance" },
{ value: "foreign-policy", text: "Foreign Policy" },
{ value: "national-security", text: "National Security" },
{ value: "free-speech", text: "Free Speech" },
{ value: "net-neutrality", text: "Net Neutrality" },
{ value: "labor-rights", text: "Labor Rights" },
{ value: "affordable-housing", text: "Affordable Housing" },
{ value: "environmental-protection", text: "Environmental Protection" },
{ value: "space-exploration", text: "Space Exploration" },
{ value: "drug-policy-reform", text: "Drug Policy Reform" },
{ value: "veterans-affairs", text: "Veterans' Affairs" },
{ value: "senior-citizen-rights", text: "Senior Citizen Rights" },
];
const issueTitlesMap = new Map(ISSUES.map(issue => [issue.value, issue.text]));
const donationLinks = {
"gun-control": [
{ "name": "Brady United", "url": "https://www.bradyunited.org/donate" },
{ "name": "Everytown for Gun Safety", "url": "https://www.everytown.org/donate/" },
{ "name": "Moms Demand Action", "url": "https://momsdemandaction.org/donate/"}
],
"gun-rights": [
{ "name": "Gun Owners of America", "url": "https://www.gunowners.org/donate/" },
{ "name": "National Rifle Association (NRA)", "url": "https://donate.nra.org/donate" },
{ "name": "Second Amendment Foundation (SAF)", "url": "https://www.saf.org/donate/"}
],
"privacy": [
{ "name": "Electronic Frontier Foundation (EFF)", "url": "https://www.eff.org/donate" },
{ "name": "ACLU", "url": "https://www.aclu.org/donate/join-aclu-renew-your-membership" },
{ "name": "Privacy Rights Clearinghouse", "url": "https://privacyrights.org/donate"}
],
"immigration": [
{ "name": "ACLU Immigrants' Rights Project", "url": "https://www.aclu.org/issues/immigrants-rights/donate" },
{ "name": "National Immigration Law Center (NILC)", "url": "https://www.nilc.org/donate/" },
{ "name": "Kids in Need of Defense (KIND)", "url": "https://supportkind.org/donate/"}
],
"climate-change": [
{ "name": "Earthjustice", "url": "https://www.earthjustice.org/donate" },
{ "name": "Natural Resources Defense Council (NRDC)", "url": "https://www.nrdc.org/donate" },
{ "name": "Environmental Defense Fund (EDF)", "url": "https://www.edf.org/donate"}
],
"healthcare": [
{ "name": "Families USA", "url": "https://www.familiesusa.org/donate/" },
{ "name": "Planned Parenthood", "url": "https://www.plannedparenthood.org/donate" },
{ "name": "Doctors Without Borders", "url": "https://www.doctorswithoutborders.org/donate"}
],
"racial-justice": [
{ "name": "NAACP Legal Defense and Educational Fund (NAACP LDF)", "url": "https://www.naacpldf.org/donate/" },
{ "name": "Southern Poverty Law Center (SPLC)", "url": "https://www.splcenter.org/support-us" },
{ "name": "Black Lives Matter Global Network Foundation", "url": "https://blacklivesmatter.com/donate/"}
],
"lgbtq-rights": [
{ "name": "Lambda Legal", "url": "https://www.lambdalegal.org/donate" },
{ "name": "Human Rights Campaign (HRC)", "url": "https://www.hrc.org/donate" },
{ "name": "The Trevor Project", "url": "https://www.thetrevorproject.org/donate/"}
],
"economic-inequality": [
{ "name": "National Employment Law Project (NELP)", "url": "https://www.nelp.org/donate/" },
{ "name": "Economic Policy Institute (EPI)", "url": "https://www.epi.org/donate/" },
{ "name": "Oxfam America", "url": "https://www.oxfamamerica.org/donate/"}
],
"reproductive-rights": [
{ "name": "Reproductive Freedom for All", "url": "https://act.reprofreedomforall.org/a/donate" },
{ "name": "Planned Parenthood", "url": "https://www.plannedparenthood.org/donate" },
{ "name": "National Abortion Federation (NAF)", "url": "https://prochoice.org/donate/"}
],
"education-reform": [
{ "name": "The Education Trust", "url": "https://edtrust.org/donate/" },
{ "name": "DonorsChoose", "url": "https://www.donorschoose.org/donate" },
{ "name": "Teach for America", "url": "https://www.teachforamerica.org/donate"}
],
"criminal-justice": [
{ "name": "Justice in Aging", "url": "https://www.justiceinaging.org/donate/" },
{ "name": "The Sentencing Project", "url": "https://www.sentencingproject.org/donate/" },
{ "name": "Innocence Project", "url": "https://innocenceproject.org/donate/" },
{ "name": "Drug Policy Alliance", "url": "https://drugpolicy.org/donate" }
],
"voting-rights": [
{ "name": "Brennan Center for Justice", "url": "https://www.brennancenter.org/donate" },
{ "name": "Fair Fight", "url": "https://fairfight.com/invest/" },
{ "name": "League of Women Voters", "url": "https://www.lwv.org/donate"}
],
"campaign-finance": [
{ "name": "Campaign Legal Center", "url": "https://campaignlegal.org/donate" },
{ "name": "OpenSecrets (Center for Responsive Politics)", "url": "https://www.opensecrets.org/donate" },
{ "name": "Common Cause", "url": "https://www.commoncause.org/donate/"}
],
"foreign-policy": [
{ "name": "Council on Foreign Relations (CFR)", "url": "https://www.cfr.org/support-cfr" },
{ "name": "Carnegie Endowment for International Peace", "url": "https://carnegieendowment.org/donate/" },
{ "name": "United Nations Foundation", "url": "https://unfoundation.org/donate/"}
],
"national-security": [
{ "name": "Center for Strategic and International Studies (CSIS)", "url": "https://www.csis.org/donate" },
{ "name": "Atlantic Council", "url": "https://www.atlanticcouncil.org/support/" },
{ "name": "Union of Concerned Scientists (UCS)", "url": "https://www.ucsusa.org/donate"}
],
"free-speech": [
{ "name": "Foundation for Individual Rights and Expression (FIRE)", "url": "https://www.thefire.org/donate" },
{ "name": "ACLU", "url": "https://www.aclu.org/donate/join-aclu-renew-your-membership" },
{ "name": "PEN America", "url": "https://pen.org/donate/"}
],
"net-neutrality": [
{ "name": "Free Press", "url": "https://www.freepress.net/donate/" },
{ "name": "Electronic Frontier Foundation (EFF)", "url": "https://www.eff.org/donate" },
{ "name": "Demand Progress", "url": "https://demandprogress.org/donate/"}
],
"labor-rights": [
{ "name": "AFL-CIO", "url": "https://aflcio.org/about/ways-to-give" },
{ "name": "National Labor Law Project", "url": "https://www.nationallaborlawproject.org/donate/"},
{ "name": "United Farm Workers (UFW)", "url": "https://ufw.org/donate/"}
],
"affordable-housing": [
{ "name": "National Low Income Housing Coalition (NLIHC)", "url": "https://nlihc.org/donate" },
{ "name": "Habitat for Humanity", "url": "https://www.habitat.org/donate" },
{ "name": "Enterprise Community Partners", "url": "https://www.enterprisecommunity.org/donate"}
],
"environmental-protection": [
{ "name": "Natural Resources Defense Council (NRDC)", "url": "https://www.nrdc.org/donate" },
{ "name": "Sierra Club", "url": "https://www.sierraclub.org/donate" },
{ "name": "World Wildlife Fund (WWF)", "url": "https://www.worldwildlife.org/donate"}
],
"space-exploration": [
{ "name": "The Planetary Society", "url": "https://www.planetary.org/donate" },
{ "name": "National Space Society", "url": "https://space.nss.org/donate/" },
{ "name": "Space Foundation", "url": "https://www.spacefoundation.org/support-us/"}
],
"drug-policy-reform": [
{ "name": "Drug Policy Alliance", "url": "https://drugpolicy.org/donate" },
{ "name": "NORML", "url": "https://norml.org/donate/" },
{ "name": "Marijuana Policy Project (MPP)", "url": "https://www.mpp.org/donate/"}
],
"veterans-affairs": [
{ "name": "Iraq and Afghanistan Veterans of America (IAVA)", "url": "https://www.iava.org/donate/" },
{ "name": "Wounded Warrior Project", "url": "https://www.woundedwarrior.org/donate" },
{ "name": "Gary Sinise Foundation", "url": "https://www.garysinisefoundation.org/donate"}
],
"senior-citizen-rights": [
{ "name": "Justice in Aging", "url": "https://www.justiceinaging.org/donate/" },
{ "name": "AARP Foundation", "url": "https://www.aarp.org/giving/" },
{ "name": "National Council on Aging (NCOA)", "url": "https://www.ncoa.org/donate"}
],
"disability-rights": [
{ "name": "Disability Rights Education and Defense Fund (DREDF)", "url": "https://dredf.org/donate/" },
{ "name": "American Association of People with Disabilities (AAPD)", "url": "https://www.aapd.com/donate/" }
],
"animal-welfare": [
{ "name": "ASPCA (American Society for the Prevention of Cruelty to Animals)", "url": "https://www.aspca.org/donate" },
{ "name": "Humane Society of the United States (HSUS)", "url": "https://www.humanesociety.org/donate" }
],
"arts-culture": [
{ "name": "National Endowment for the Arts (NEA)", "url": "https://www.arts.gov/support-nea" },
{ "name": "Americans for the Arts", "url": "https://www.americansforthearts.org/donate" }
],
"child-welfare": [
{ "name": "Childhelp", "url": "https://www.childhelp.org/donate/" },
{ "name": " Prevent Child Abuse America", "url": "https://preventchildabuse.org/donate/" }
]
};
let praxisBanner = null;
const sendSentimentAnalysisRequest = async () => {
const response = await chrome.runtime.sendMessage({
type: "ANALYZE_SENTIMENT",
payload: { headline: headlineText }
});
if (response.type === "ANALYSIS_RESULT" && response.result && response.result.sentiment) {
const sentiment = response.result.sentiment;
if (sentiment === 'negative' || sentiment === 'depressing' || sentiment === 'worrying') {
sendCategoryAnalysisRequest();
} else {
return;
}
} else if (response.type === "LLM_ERROR") {
return;
} else {
return;
}
};
const sendCategoryAnalysisRequest = async () => {
const storageApi = typeof browser !== 'undefined' && browser.storage ? browser.storage.local : (typeof chrome !== 'undefined' && chrome.storage ? chrome.storage.sync : null);
let selectedIssues = [];
if (storageApi) {
try {
const data = await storageApi.get('selected_issues');
if (data && data.selected_issues) {
selectedIssues = data.selected_issues;
}
} catch (error) {
}
}
const response = await chrome.runtime.sendMessage({
type: "ANALYZE_HEADLINE_CATEGORY",
payload: {
headline: headlineText,
selectedIssues: selectedIssues
}
});
if (response.type === "ANALYSIS_RESULT" && response.result && response.result.categories && response.result.categories.length > 0) {
let donateLinksHtml = '';
response.result.categories.forEach(category => {
const organizations = donationLinks[category];
if (organizations && organizations.length > 0) {
const niceCategoryTitle = issueTitlesMap.get(category) || category.replace(/-/g, ' ');
donateLinksHtml += `<strong>${niceCategoryTitle}:</strong> `;
const categoryOrgLinks = organizations.map(org =>
`<a href="${org.url}" target="_blank" style="color: #60a5fa; text-decoration: underline;">${org.name}</a>`
).join(', ');
donateLinksHtml += `${categoryOrgLinks}<br>`;
} else {
}
});
if (donateLinksHtml) {
praxisBanner = createPraxisBanner(`Based on this news article, you can donate to the following impact organizations:<br>${donateLinksHtml}`);
} else {
}
} else if (response.type === "LLM_ERROR") {
return;
} else {
return;
}
};
sendSentimentAnalysisRequest();
})();

324
manifest.json Normal file
View File

@ -0,0 +1,324 @@
{
"manifest_version": 3,
"name": "Praxis",
"version": "1.0",
"description": "Praxis displays suggested donations for concerning news headlines.",
"permissions": ["storage", "activeTab", "scripting"],
"host_permissions": [
"*://*.abcnews.go.com/*",
"*://*.aljazeera.com/*",
"*://*.apnews.com/*",
"*://*.axios.com/*",
"*://*.bbc.co.uk/*",
"*://*.bbc.com/*",
"*://*.bloomberg.com/*",
"*://*.bostonglobe.com/*",
"*://*.breitbart.com/*",
"*://*.businessinsider.com/*",
"*://*.c-span.org/*",
"*://*.cbsnews.com/*",
"*://*.chicagotribune.com/*",
"*://*.cnet.com/*",
"*://*.cnbc.com/*",
"*://*.cnn.com/*",
"*://*.csmonitor.com/*",
"*://*.dailycaller.com/*",
"*://*.dailymail.co.uk/*",
"*://*.drudgereport.com/*",
"*://*.dw.com/*",
"*://*.economist.com/*",
"*://*.euronews.com/*",
"*://*.fivethirtyeight.com/*",
"*://*.forbes.com/*",
"*://*.foreignpolicy.com/*",
"*://*.foxnews.com/*",
"*://*.france24.com/*",
"*://*.ft.com/*",
"*://*.hollywoodreporter.com/*",
"*://*.huffpost.com/*",
"*://*.independent.co.uk/*",
"*://*.indiatimes.com/*",
"*://*.insider.com/*",
"*://*.latimes.com/*",
"*://*.marketwatch.com/*",
"*://*.msnbc.com/*",
"*://*.nationalgeographic.com/*",
"*://*.nationalreview.com/*",
"*://*.nbcnews.com/*",
"*://*.newrepublic.com/*",
"*://*.news.com.au/*",
"*://*.newsweek.com/*",
"*://*.newyorker.com/*",
"*://news.google.com/*",
"*://news.yahoo.com/*",
"*://*.npr.org/*",
"*://*.nypost.com/*",
"*://*.nytimes.com/*",
"*://*.patch.com/*",
"*://*.pbs.org/*",
"*://*.politico.com/*",
"*://*.propublica.org/*",
"*://*.qz.com/*",
"*://*.rawstory.com/*",
"*://*.reuters.com/*",
"*://*.salon.com/*",
"*://*.scientificamerican.com/*",
"*://*.scmp.com/*",
"*://*.semafor.com/*",
"*://*.sfgate.com/*",
"*://*.slate.com/*",
"*://*.techcrunch.com/*",
"*://*.theatlantic.com/*",
"*://*.thebulwark.com/*",
"*://*.thedailybeast.com/*",
"*://*.theepochtimes.com/*",
"*://*.thegatewaypundit.com/*",
"*://*.theguardian.com/*",
"*://*.thehill.com/*",
"*://*.theintercept.com/*",
"*://*.thetimes.co.uk/*",
"*://*.theverge.com/*",
"*://*.theweek.com/*",
"*://time.com/*",
"*://*.usatoday.com/*",
"*://*.usnews.com/*",
"*://*.variety.com/*",
"*://*.vice.com/*",
"*://*.vox.com/*",
"*://*.washingtonpost.com/*",
"*://*.wired.com/*",
"*://*.wsj.com/*",
"*://*.adweek.com/*",
"*://*.axios.com/*",
"*://*.bioreports.com/*",
"*://*.businesswire.com/*",
"*://*.cbslocal.com/*",
"*://*.chron.com/*",
"*://*.darkreading.com/*",
"*://*.deadline.com/*",
"*://*.denverpost.com/*",
"*://*.digiday.com/*",
"*://*.elle.com/*",
"*://*.eonline.com/*",
"*://*.engadget.com/*",
"*://*.esquire.com/*",
"*://*.etonline.com/*",
"*://*.foxbusiness.com/*",
"*://*.gq.com/*",
"*://*.grubstreet.com/*",
"*://*.houstonchronicle.com/*",
"*://*.ign.com/*",
"*://*.intelligencer.com/*",
"*://*.jalopnik.com/*",
"*://*.jezebel.com/*",
"*://*.lifehacker.com/*",
"*://*.mashable.com/*",
"*://*.mediaite.com/*",
"*://*.miamiherald.com/*",
"*://*.motherjones.com/*",
"*://*.newrepublic.com/*",
"*://*.newsy.com/*",
"*://*.nymag.com/*",
"*://*.oregonlive.com/*",
"*://*.pcmag.com/*",
"*://*.people.com/*",
"*://*.philly.com/*",
"*://*.refinery29.com/*",
"*://*.rollingstone.com/*",
"*://*.rt.com/*",
"*://*.seattletimes.com/*",
"*://*.sfchronicle.com/*",
"*://*.sltrib.com/*",
"*://*.sportingnews.com/*",
"*://*.startribune.com/*",
"*://*.techradar.com/*",
"*://*.thecut.com/*",
"*://*.thedailybeast.com/*",
"*://*.theglobeandmail.com/*",
"*://*.theintercept.com/*",
"*://*.theonion.com/*",
"*://*.theverge.com/*",
"*://*.thisisinsider.com/*",
"*://*.tomsguide.com/*",
"*://*.twitchy.com/*",
"*://*.vulture.com/*",
"*://*.washingtonexaminer.com/*",
"*://*.wcvb.com/*",
"*://*.wral.com/*",
"*://*.wtae.com/*"
],
"action": {
"default_popup": "popup.html"
},
"background": {
"service_worker": "background.js",
"type": "module"
},
"icons": {
"16": "praxis-logo-16.jpg",
"48": "praxis-logo-48.jpg",
"128": "praxis-logo-128.jpg"
},
"content_scripts": [
{
"matches": [
"*://*.abcnews.go.com/*",
"*://*.aljazeera.com/*",
"*://*.apnews.com/*",
"*://*.axios.com/*",
"*://*.bbc.co.uk/*",
"*://*.bbc.com/*",
"*://*.bloomberg.com/*",
"*://*.bostonglobe.com/*",
"*://*.breitbart.com/*",
"*://*.businessinsider.com/*",
"*://*.c-span.org/*",
"*://*.cbsnews.com/*",
"*://*.chicagotribune.com/*",
"*://*.cnet.com/*",
"*://*.cnbc.com/*",
"*://*.cnn.com/*",
"*://*.csmonitor.com/*",
"*://*.dailycaller.com/*",
"*://*.dailymail.co.uk/*",
"*://*.drudgereport.com/*",
"*://*.dw.com/*",
"*://*.economist.com/*",
"*://*.euronews.com/*",
"*://*.fivethirtyeight.com/*",
"*://*.forbes.com/*",
"*://*.foreignpolicy.com/*",
"*://*.foxnews.com/*",
"*://*.france24.com/*",
"*://*.ft.com/*",
"*://*.hollywoodreporter.com/*",
"*://*.huffpost.com/*",
"*://*.independent.co.uk/*",
"*://*.indiatimes.com/*",
"*://*.insider.com/*",
"*://*.latimes.com/*",
"*://*.marketwatch.com/*",
"*://*.msnbc.com/*",
"*://*.nationalgeographic.com/*",
"*://*.nationalreview.com/*",
"*://*.nbcnews.com/*",
"*://*.newrepublic.com/*",
"*://*.news.com.au/*",
"*://*.newsweek.com/*",
"*://*.newyorker.com/*",
"*://news.google.com/*",
"*://news.yahoo.com/*",
"*://*.npr.org/*",
"*://*.nypost.com/*",
"*://*.nytimes.com/*",
"*://*.patch.com/*",
"*://*.pbs.org/*",
"*://*.politico.com/*",
"*://*.propublica.org/*",
"*://*.qz.com/*",
"*://*.rawstory.com/*",
"*://*.reuters.com/*",
"*://*.salon.com/*",
"*://*.scientificamerican.com/*",
"*://*.scmp.com/*",
"*://*.semafor.com/*",
"*://*.sfgate.com/*",
"*://*.slate.com/*",
"*://*.techcrunch.com/*",
"*://*.theatlantic.com/*",
"*://*.thebulwark.com/*",
"*://*.thedailybeast.com/*",
"*://*.theepochtimes.com/*",
"*://*.thegatewaypundit.com/*",
"*://*.theguardian.com/*",
"*://*.thehill.com/*",
"*://*.theintercept.com/*",
"*://*.thetimes.co.uk/*",
"*://*.theverge.com/*",
"*://*.theweek.com/*",
"*://time.com/*",
"*://*.usatoday.com/*",
"*://*.usnews.com/*",
"*://*.variety.com/*",
"*://*.vice.com/*",
"*://*.vox.com/*",
"*://*.washingtonpost.com/*",
"*://*.wired.com/*",
"*://*.wsj.com/*",
"*://*.adweek.com/*",
"*://*.axios.com/*",
"*://*.bioreports.com/*",
"*://*.businesswire.com/*",
"*://*.cbslocal.com/*",
"*://*.chron.com/*",
"*://*.darkreading.com/*",
"*://*.deadline.com/*",
"*://*.denverpost.com/*",
"*://*.digiday.com/*",
"*://*.elle.com/*",
"*://*.eonline.com/*",
"*://*.engadget.com/*",
"*://*.esquire.com/*",
"*://*.etonline.com/*",
"*://*.foxbusiness.com/*",
"*://*.gq.com/*",
"*://*.grubstreet.com/*",
"*://*.houstonchronicle.com/*",
"*://*.ign.com/*",
"*://*.intelligencer.com/*",
"*://*.jalopnik.com/*",
"*://*.jezebel.com/*",
"*://*.lifehacker.com/*",
"*://*.mashable.com/*",
"*://*.mediaite.com/*",
"*://*.miamiherald.com/*",
"*://*.motherjones.com/*",
"*://*.newrepublic.com/*",
"*://*.newsy.com/*",
"*://*.nymag.com/*",
"*://*.oregonlive.com/*",
"*://*.pcmag.com/*",
"*://*.people.com/*",
"*://*.philly.com/*",
"*://*.refinery29.com/*",
"*://*.rollingstone.com/*",
"*://*.rt.com/*",
"*://*.seattletimes.com/*",
"*://*.sfchronicle.com/*",
"*://*.sltrib.com/*",
"*://*.sportingnews.com/*",
"*://*.startribune.com/*",
"*://*.techradar.com/*",
"*://*.thecut.com/*",
"*://*.thedailybeast.com/*",
"*://*.theglobeandmail.com/*",
"*://*.theintercept.com/*",
"*://*.theonion.com/*",
"*://*.theverge.com/*",
"*://*.thisisinsider.com/*",
"*://*.tomsguide.com/*",
"*://*.twitchy.com/*",
"*://*.vulture.com/*",
"*://*.washingtonexaminer.com/*",
"*://*.wcvb.com/*",
"*://*.wral.com/*",
"*://*.wtae.com/*"
],
"js": ["content.js"]
}
],
"web_accessible_resources": [
{
"resources": [
"sandbox.html",
"sandbox.js",
"transformers/*",
"models/nli-deberta-v3-small/*"
],
"matches": ["<all_urls>"]
}
],
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';"
}
}

3366
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

19
package.json Normal file
View File

@ -0,0 +1,19 @@
{
"name": "praxis",
"version": "1.0.0",
"scripts": {
"clean": "rm -rf dist",
"copy:static": "cpx \"./{manifest.json,*.html,*.js,*.jpg}\" dist",
"copy:transformers": "cpx \"./node_modules/@huggingface/transformers/dist/*\" dist/transformers",
"copy:model_temp": "cpx \"./nli-deberta-v3-small/onnx/model.onnx\" dist/models/nli-deberta-v3-small/onnx/",
"rename:model": "mv dist/models/nli-deberta-v3-small/onnx/model.onnx dist/models/nli-deberta-v3-small/onnx/model.onnx_quantized.onnx",
"copy:config_and_tokenizer": "cpx \"./nli-deberta-v3-small/*.json\" dist/models/nli-deberta-v3-small/",
"build": "npm run clean && npm run copy:static && npm run copy:transformers && npm run copy:config_and_tokenizer && npm run copy:model_temp && npm run rename:model"
},
"devDependencies": {
"cpx": "^1.5.0"
},
"dependencies": {
"@huggingface/transformers": "^3.6.1"
}
}

170
popup.html Normal file
View File

@ -0,0 +1,170 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Praxis</title>
<style>
body {
background-color: #000000;
color: #ffffff;
width: 320px;
font-family: sans-serif;
}
#root {
padding: 16px;
}
.flow-header h1 {
font-size: 1.5rem;
font-weight: bold;
text-align: center;
margin-bottom: 8px;
}
.flow-header p {
text-align: center;
color: #cccccc;
margin-bottom: 16px;
}
#logo {
display: block;
margin: 0 auto 8px auto;
height: 128px;
width: auto;
}
#onboarding-issues-container,
#confirmation-issues-list-wrapper { /* Apply to the new wrapper */
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
margin-bottom: 16px;
}
#confirmation-issues-list-wrapper {
display: flex;
justify-content: center; /* Center the list wrapper */
width: 100%; /* Ensure it takes full width to allow centering */
}
#confirmation-issues-list {
display: flex;
flex-direction: column;
gap: 4px;
margin-bottom: 16px;
padding-left: 20px;
list-style-position: inside; /* Keep bullets inside the list item for better alignment */
text-align: left; /* Ensure text remains left justified */
width: fit-content; /* Allow the list to shrink to its content's width */
}
#confirmation-issues-list li {
list-style-type: disc;
color: #ffffff;
}
.issue-button {
border-radius: 8px;
border: none;
background-color: #222222;
padding: 8px 16px;
font-size: 0.875rem;
font-weight: 600;
color: #ffffff;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.2);
transition: all 200ms ease-in-out;
cursor: pointer;
}
.issue-button:hover {
background-color: #333333;
}
.issue-button:focus {
outline: none;
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
}
.issue-button.selected {
background-color: #003366;
border-color: #0055aa;
color: #ffffff;
}
.action-button {
width: 100%;
border-radius: 4px;
padding: 8px 16px;
font-weight: bold;
color: #ffffff;
cursor: pointer;
margin-top: 8px;
border: none;
}
#save-onboarding-issues {
background-color: #004d99;
}
#save-onboarding-issues:hover {
background-color: #003366;
}
#confirm-issues {
background-color: #28a745;
}
#confirm-issues:hover {
background-color: #218838;
}
#reselect-issues {
background-color: #6c757d;
}
#reselect-issues:hover {
background-color: #5a6268;
}
#onboarding-flow,
#confirmation-flow {
display: none;
}
</style>
</head>
<body>
<div id="root">
<div id="onboarding-flow">
<div class="flow-header">
<img id="logo" src="praxis-logo.jpg" alt="Praxis Logo" />
<h1>Welcome to Praxis</h1>
<p>Select the issues you care about:</p>
</div>
<div id="onboarding-issues-container">
</div>
<button id="save-onboarding-issues" class="action-button">Save Preferences</button>
</div>
<div id="confirmation-flow">
<div class="flow-header">
<img id="logo" src="praxis.jpg" alt="Praxis Logo" />
<p>Status: Active</p>
<p>Here are the issues you've chosen:</p>
</div>
<div id="confirmation-issues-list-wrapper">
<ul id="confirmation-issues-list">
<li>No issues selected yet.</li>
</ul>
</div>
<button id="confirm-issues" class="action-button">Close</button>
<button id="reselect-issues" class="action-button">Reselect Issues</button>
</div>
</div>
<script src="browser-polyfill.min.js"></script>
<script src="popup.js"></script>
</body>
</html>

198
popup.js Normal file
View File

@ -0,0 +1,198 @@
document.addEventListener('DOMContentLoaded', () => {
const onboardingFlow = document.getElementById('onboarding-flow');
const confirmationFlow = document.getElementById('confirmation-flow');
const onboardingIssuesContainer = document.getElementById('onboarding-issues-container');
let onboardingIssueButtons;
const saveOnboardingButton = document.getElementById('save-onboarding-issues');
const confirmationIssuesList = document.getElementById('confirmation-issues-list');
const confirmButton = document.getElementById('confirm-issues');
const reselectButton = document.getElementById('reselect-issues');
const statusParagraph = confirmationFlow.querySelector('.flow-header p:nth-of-type(1)');
const ISSUES = [
{ value: "gun-control", text: "Gun Control" },
{ value: "gun-rights", text: "Gun Rights" },
{ value: "privacy", text: "Electronic Privacy" },
{ value: "immigration", text: "Immigration" },
{ value: "climate-change", text: "Climate Change" },
{ value: "healthcare", text: "Healthcare" },
{ value: "racial-justice", text: "Racial Justice" },
{ value: "lgbtq-rights", text: "LGBTQ+ Rights" },
{ value: "economic-inequality", text: "Economic Inequality" },
{ value: "reproductive-rights", text: "Reproductive Rights" },
{ value: "education-reform", text: "Education Reform" },
{ value: "criminal-justice", text: "Criminal Justice" },
{ value: "voting-rights", text: "Voting Rights" },
{ value: "campaign-finance", text: "Campaign Finance" },
{ value: "foreign-policy", text: "Foreign Policy" },
{ value: "national-security", text: "National Security" },
{ value: "free-speech", text: "Free Speech" },
{ value: "net-neutrality", text: "Net Neutrality" },
{ value: "labor-rights", text: "Labor Rights" },
{ value: "affordable-housing", text: "Affordable Housing" },
{ value: "environmental-protection", text: "Environmental Protection" },
{ value: "space-exploration", text: "Space Exploration" },
{ value: "drug-policy-reform", text: "Drug Policy Reform" },
{ value: "veterans-affairs", text: "Veterans' Affairs" },
{ value: "senior-citizen-rights", text: "Senior Citizen Rights" },
];
const generateIssueButtons = () => {
ISSUES.forEach(issue => {
const button = document.createElement('button');
button.className = 'issue-button';
button.setAttribute('data-value', issue.value);
button.setAttribute('aria-pressed', 'false');
button.textContent = issue.text;
onboardingIssuesContainer.appendChild(button);
});
onboardingIssueButtons = onboardingIssuesContainer.querySelectorAll('.issue-button');
};
generateIssueButtons();
const getStorageApi = () => {
if (typeof browser !== 'undefined' && browser.storage) {
return browser.storage.local;
}
else if (typeof chrome !== 'undefined' && chrome.storage) {
return chrome.storage.sync;
}
return null;
};
const storageApi = getStorageApi();
const showFlow = (flow) => {
onboardingFlow.style.display = 'none';
confirmationFlow.style.display = 'none';
flow.style.display = 'block';
};
const populateConfirmationList = (issues) => {
confirmationIssuesList.innerHTML = '';
if (issues && issues.length > 0) {
issues.forEach(issueValue => {
const issueObj = ISSUES.find(issue => issue.value === issueValue);
const listItem = document.createElement('li');
listItem.textContent = issueObj.text;
confirmationIssuesList.appendChild(listItem);
});
} else {
const listItem = document.createElement('li');
listItem.textContent = 'No issues selected yet.';
confirmationIssuesList.appendChild(listItem);
}
};
const updateStatusDisplay = (isActive) => {
if (statusParagraph) {
statusParagraph.textContent = `Status: ${isActive ? 'Active' : 'Inactive'}`;
statusParagraph.style.color = isActive ? '#10B981' : '#EF4444';
statusParagraph.style.fontWeight = 'bold';
}
confirmButton.textContent = isActive ? 'Deactivate Praxis' : 'Activate Praxis';
confirmButton.style.backgroundColor = isActive ? '#EF4444' : '#10B981';
};
onboardingIssuesContainer.addEventListener('click', (event) => {
if (event.target.classList.contains('issue-button')) {
const button = event.target;
button.classList.toggle('selected');
const isSelected = button.classList.contains('selected');
button.setAttribute('aria-pressed', isSelected);
}
});
saveOnboardingButton.addEventListener('click', () => {
const selectedIssues = Array.from(onboardingIssuesContainer.querySelectorAll('.issue-button.selected'))
.map(button => button.dataset.value);
if (storageApi) {
storageApi.set({
selected_issues: selectedIssues,
onboarding_complete: true,
praxis_active: true
})
.then(() => {
populateConfirmationList(selectedIssues);
updateStatusDisplay(true);
showFlow(confirmationFlow);
})
.catch(error => {
alert('Error saving data. Please try again. See console for details.');
});
} else {
alert('Your browser does not support the necessary storage features.');
}
});
confirmButton.addEventListener('click', () => {
if (storageApi) {
storageApi.get('praxis_active')
.then(data => {
const newStatus = typeof data.praxis_active === 'boolean' ? !data.praxis_active : false;
storageApi.set({ praxis_active: newStatus })
.then(() => {
updateStatusDisplay(newStatus);
})
.catch(error => {});
})
.catch(error => {});
}
});
reselectButton.addEventListener('click', () => {
onboardingIssueButtons.forEach(button => {
button.classList.remove('selected');
button.setAttribute('aria-pressed', false);
});
if (storageApi) {
storageApi.get('selected_issues')
.then(data => {
if (data && data.selected_issues) {
data.selected_issues.forEach(savedIssue => {
const button = onboardingIssuesContainer.querySelector(`.issue-button[data-value="${savedIssue}"]`);
if (button) {
button.classList.add('selected');
button.setAttribute('aria-pressed', true);
}
});
}
})
.catch(error => {});
}
showFlow(onboardingFlow);
});
if (storageApi) {
storageApi.get(['selected_issues', 'onboarding_complete', 'praxis_active'])
.then(data => {
const isActive = typeof data.praxis_active === 'boolean' ? data.praxis_active : true;
if (data.onboarding_complete) {
populateConfirmationList(data.selected_issues || []);
updateStatusDisplay(isActive);
showFlow(confirmationFlow);
} else {
if (data.selected_issues) {
data.selected_issues.forEach(savedIssue => {
const button = onboardingIssuesContainer.querySelector(`.issue-button[data-value="${savedIssue}"]`);
if (button) {
button.classList.add('selected');
button.setAttribute('aria-pressed', true);
}
});
}
showFlow(onboardingFlow);
}
})
.catch(error => {
updateStatusDisplay(true);
showFlow(onboardingFlow);
});
} else {
updateStatusDisplay(true);
showFlow(onboardingFlow);
}
});

BIN
praxis-logo-128.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
praxis-logo-16.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 B

BIN
praxis-logo-48.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
praxis-logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
praxis.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
praxis.mp4 Normal file

Binary file not shown.

43
readme.md Normal file
View File

@ -0,0 +1,43 @@
![Praxis Logo](https://gitea.bubbletea.dev/shibao/praxis/raw/branch/main/praxis.jpg)
**Sick of the endless scroll, the constant barrage of soul-crushing headlines that leave you numb and powerless?** "Awareness" is a goddamn lie. It changes nothing. It's time to ditch the passive outrage and actually *do* something.
## What is Praxis?
Praxis isn't another empty gesture. It's a browser extension built to weaponize your frustration, turning it into direct, meaningful impact. When you see news of injustice or disaster, Praxis doesn't leave you stewing in anger, fear and depression. It **immediately connects you with the boots-on-the-ground organizations** actively fighting that specific issue with a donation link.
This extension uses a **lightweight, efficient AI in your browser**. That means:
* **Your Privacy is Paramount:** Your data stays with you, always.
* **Uncompromised Speed:** No external servers, no bullshit delays.
* **Precision Action:** Every suggestion is guaranteed to bring more change than
"raising awareness" or "contacting your senators".
## Why Praxis? Why Now?
The system thrives on your despair and inaction. Praxis is here to smash that.
* **Awareness Isn't Enough:** Everyone's "aware." What's missing is a goddamn effective punch.
* **Direct Impact, No Excuses:** Praxis hooks you up with organizations that actually get results. No wasted energy, no empty promises.
* **Fight Back:** Turn the despair of a headline into a powerful blow against the root cause.
* **Immediate Solutions:** The means to act are shoved right in front of you, precisely when you need 'em.
* **Beyond Online Debates:** Ditch the performative outrage. Start making real change.
## Take Control with Praxis.
Don't let the news consume you. Let it fuel your fire. Convert your justifiable rage into effective resistance.
**Download Praxis today and make damn sure every click contributes to a better world.**
Chrome: Todo
Firefox: Todo
**Stop watching the world burn. Start making a difference.**
# Demo
<video controls autoplay loop muted playsinline>
<source src="https://gitea.bubbletea.dev/shibao/praxis/raw/branch/main/praxis.mp4" type="video/mp4">
Your browser does not support the video tag. Here is a <a href="https://gitea.bubbletea.dev/shibao/praxis/raw/branch/main/praxis.mp4">link to the video</a> instead.
</video>