Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
727dbe2
Implement Groq backend with API integration and error handling
QuantumChemist Aug 30, 2025
b03779d
Add .env to .gitignore to prevent sensitive data exposure
QuantumChemist Aug 30, 2025
2984c79
Add async function to handle Groq API requests
QuantumChemist Aug 30, 2025
e316e4e
Add AI Q&A feature to sidebar for term inquiries
QuantumChemist Aug 30, 2025
9aa0517
Enhance AI Q&A input styling for better visibility and user experience
QuantumChemist Aug 30, 2025
33d8f93
Add package.json with initial dependencies for the project
QuantumChemist Aug 30, 2025
d6a2b14
Add dotenv dependency to package.json and package-lock.json
QuantumChemist Aug 30, 2025
5958cb0
Refactor Groq API request handling and improve logging for better deb…
QuantumChemist Aug 30, 2025
1e4e46b
Update AI Q&A answer styling for improved readability
QuantumChemist Aug 30, 2025
5d661d6
Refactor Groq API request to include system prompt and improve messag…
QuantumChemist Aug 30, 2025
81f8450
Enhance Q&A functionality by adding in-memory history tracking and im…
QuantumChemist Aug 30, 2025
c2d73f1
Refactor system prompt to remove redundant phrasing for clarity
QuantumChemist Aug 30, 2025
b7c1865
Refactor chat history construction to streamline message handling for…
QuantumChemist Aug 30, 2025
7373ab8
Refactor Groq API handling to improve system prompt and increase Q&A …
QuantumChemist Aug 30, 2025
7f30d4e
Improve sidebar button layout and styling for better usability
QuantumChemist Aug 30, 2025
85c7094
Update sidebar header layout to improve readability
QuantumChemist Aug 30, 2025
438e11a
Update input placeholder to indicate local server requirement for AI …
QuantumChemist Aug 30, 2025
fc4f160
Add experimental AI Q&A feature with local server requirement and usa…
QuantumChemist Aug 30, 2025
ef98a8d
Update AI Q&A section to clarify local server requirement in the UI
QuantumChemist Aug 30, 2025
12855dd
Reduce in-memory history limit for Q&A from 12 to 3
QuantumChemist Aug 30, 2025
51a1dab
Merge branch 'SimonNir:main' into main
QuantumChemist Sep 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.env
node_modules/
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,18 @@ HypeLessLi has been updated with the following improvements over the original ve

---


## Experimental: AI Q&A (Optional)

HypeLessLi includes an **experimental AI Q&A feature** in the sidebar, allowing you to ask questions about hype terms and get suggestions for more objective alternatives.

**Note:**
- This feature requires running a local backend server (see `groq-backend.js`) and a valid Groq API key (put into a local .env file).
- If the backend is not running, the AI Q&A section will not function.
- The main extension features work independently of the AI Q&A.

---

## Authors
Dr. Xhoela Bame, Dr. Gjylije Hoti, Dr. Adibe Kingsley Mbachu, Dr. Vasilis Nikolaou, Simon Nirenberg, Klara Krmpotic, Dr. Christian Kuttner, Dr. Sudha Shankar (in alphabetical order)

Expand Down
61 changes: 57 additions & 4 deletions content.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,10 @@
const totalCount = [...termCounts.values()].reduce((a, b) => a + b, 0);
sidebar.innerHTML = `
<div id="hypeless-header">
<span>HypeLessLi v3.1 (${totalCount} found)</span>
<div>
<button id="hypeless-help">Info</button>
<button id="hypeless-toggle">Hide</button>
<span>HypeLessLi v3.1 <br> (${totalCount} found)</span>
<div style="display:flex;gap:6px;align-items:center;margin-top:4px;">
<button id="hypeless-help" style="flex:1 1 0;min-width:0;padding:4px 8px;">Info</button>
<button id="hypeless-toggle" style="flex:1 1 0;min-width:0;padding:4px 8px;">Hide</button>
</div>
</div>
<div id="hypeless-content">${itemsHTML}</div>
Expand All @@ -230,6 +230,12 @@
- Resize sidebar by dragging its edge.<br>
- Toggle with extension popup or floating button.<br>
</div>
<div id="hypeless-ai-qa" style="padding:12px 8px 8px 8px; border-top:1px solid #eee; margin-top:8px;">
<input id="hypeless-ai-input" type="text" placeholder="Ask about a term..." style="width:calc(100% - 70px);padding:4px 8px;color:#fff;background:#222;border:1px solid #444;" />
<button id="hypeless-ai-btn" style="width:56px;padding:4px 0;margin-left:4px;">Ask AI</button>
<div style="font-size:11px;color:#bbb;margin-top:2px;line-height:1.2;">AI Q&A requires<br>local server</div>
<div id="hypeless-ai-answer" style="margin-top:8px;font-size:15px;color:#fff;"></div>
</div>
`;
document.body.appendChild(sidebar);

Expand Down Expand Up @@ -258,6 +264,43 @@
}

function setupInteractions(sidebar, floatBtn, matchesByTerm, tooltip) {
// --- AI Q&A logic ---
const aiInput = sidebar.querySelector('#hypeless-ai-input');
const aiBtn = sidebar.querySelector('#hypeless-ai-btn');
const aiAnswer = sidebar.querySelector('#hypeless-ai-answer');

// On highlight click, pre-fill the input with the term
document.body.addEventListener('click', e => {
if (e.target.classList && e.target.classList.contains('hypeless-highlight')) {
const term = e.target.getAttribute('data-term');
const expl = e.target.getAttribute('data-expl');
if (aiInput) {
aiInput.value = `What does "${term}" mean in academic writing? ${expl ? 'Explanation: ' + expl : ''}`;
aiInput.focus();
}
}
});

// On AI button click, ask Groq
if (aiBtn && aiInput && aiAnswer) {
aiBtn.addEventListener('click', async () => {
const question = aiInput.value.trim();
if (!question) return;
aiBtn.disabled = true;
aiAnswer.textContent = 'Thinking...';
try {
const answer = await askGroq(question);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it will be crucial to generalize this and make it independent from groq

aiAnswer.textContent = answer;
} catch (err) {
aiAnswer.textContent = 'Error contacting AI.';
}
aiBtn.disabled = false;
});
// Enter key submits
aiInput.addEventListener('keydown', e => {
if (e.key === 'Enter') aiBtn.click();
});
}
const termPositions = new Map();
for (const term of matchesByTerm.keys()) termPositions.set(term, 0);

Expand Down Expand Up @@ -405,6 +448,16 @@
}
}

async function askGroq(question) {
const response = await fetch('http://localhost:3001/ask-groq', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ question })
});
const data = await response.json();
return data.answer;
}

// Listen for messages from background script
if (chrome.runtime && chrome.runtime.onMessage) {
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
Expand Down
84 changes: 84 additions & 0 deletions groq-backend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
const express = require('express');
const cors = require('cors');
const axios = require('axios');
require('dotenv').config();


const app = express();
app.use(cors());
app.use(express.json());

// In-memory history of last 3 Q&A
const HISTORY_LIMIT = 3;
const aiHistory = [];

app.post('/ask-groq', async (req, res) => {
const { question } = req.body;
console.log('[ask-groq] Received question:', question);


// Improved system prompt for Groq
const systemPrompt = `You are HypeLessLi, an assistant that helps users critically read scientific texts by highlighting hype-like, subjective, promotional, and vague terms. You provide clear, concise explanations for why a term is considered hype, and always suggest less hyped, more objective alternatives for any term or phrase the user asks about. If the user does not specify, always include a suggestion for a more objective or neutral alternative. If the user asks a follow-up, use the previous questions and answers in this conversation for context. Always try to resolve ambiguous or short follow-ups by referencing the last exchange.`;

// Helper: is the question a likely follow-up (short or vague)?
function isLikelyFollowup(q) {
return q.trim().length < 20 || /^(what|which|and|also|more|how about|the second|the first|that one|this one|another|other|else|too|again|continue|next|previous|last|first|second|third|fourth|fifth|sixth|seventh|eighth|ninth|tenth|it|he|she|they|him|her|them|those|these|such|so|then|now|why|how|where|when|who|whose|whom|is|are|was|were|do|does|did|can|could|should|would|will|shall|may|might|must|has|have|had|does|did|doesn't|didn't|isn't|aren't|wasn't|weren't|hasn't|haven't|hadn't|won't|wouldn't|can't|couldn't|shouldn't|mightn't|mustn't|doesnt|didnt|isnt|arent|wasnt|werent|hasnt|havent|hadnt|wont|wouldnt|cant|couldnt|shouldnt|mightnt|mustnt)\b/i.test(q.trim());
}

// Build chat history for Groq (up to HISTORY_LIMIT)
let historyPairs = aiHistory.slice(-HISTORY_LIMIT);

// If the new question is a likely follow-up, prepend the last Q&A as context
let chatHistory = [ { role: 'system', content: systemPrompt } ];
if (isLikelyFollowup(question) && historyPairs.length > 0) {
const last = historyPairs[historyPairs.length - 1];
chatHistory.push({ role: 'user', content: last.question });
chatHistory.push({ role: 'assistant', content: last.answer });
}
chatHistory = chatHistory.concat(
historyPairs.flatMap(pair => [
{ role: 'user', content: pair.question },
{ role: 'assistant', content: pair.answer }
])
);
chatHistory.push({ role: 'user', content: question });

try {
console.log('[ask-groq] Sending request to Groq API...');
const groqRes = await axios.post(
'https://api.groq.com/openai/v1/chat/completions',
{
model: 'llama-3.3-70b-versatile',
messages: chatHistory
},
{
headers: {
'Authorization': `Bearer ${process.env.GROQ_API_KEY}`,
'Content-Type': 'application/json'
}
}
);
console.log('[ask-groq] Groq API response status:', groqRes.status);
let answer = '';
if (groqRes.data && groqRes.data.choices && groqRes.data.choices[0]) {
answer = groqRes.data.choices[0].message.content;
console.log('[ask-groq] Groq API answer:', answer.slice(0, 100), '...');
} else {
console.log('[ask-groq] Groq API response missing expected data:', groqRes.data);
answer = '[No answer returned]';
}
// Add to history (keep only last HISTORY_LIMIT)
aiHistory.push({ question, answer, ts: new Date().toISOString() });
if (aiHistory.length > HISTORY_LIMIT) aiHistory.shift();
res.json({ answer });
} catch (err) {
console.error('[ask-groq] Error from Groq API:', err.response ? err.response.data : err.message);
res.status(500).json({ error: 'Groq API error', details: err.message });
}
// Endpoint to get last 7 Q&A
app.get('/ask-groq/history', (req, res) => {
res.json({ history: aiHistory });
});
});

app.listen(3001, () => console.log('Groq backend running on port 3001'));
Loading