Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 2 additions & 3 deletions popup.html
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,7 @@ <h1 class="settings-title">AI Model Settings</h1>
</div>
</div>


<script src="popup.js"></script>
<script src="prompts.js"></script>
<script src="popup.js"></script>
</body>

</html>
70 changes: 21 additions & 49 deletions popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,55 +101,28 @@ function getHint(level) {
});
}


async function fetchHint(apiKey, model, level, context) {
const cost = {
ordinary: 2,
advanced: 3,
expert: 5,
};

try {
const response = await fetch('https://api.groq.com/openai/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
},
body: JSON.stringify({
model,
messages: [
{
role: 'system',
content: `You are an AI assistant for LeetCode. Provide a ${level} hint for the following problem. Do not solve the entire problem. Just provide a hint.`,
},
{
role: 'user',
content: context,
},
],
}),
});

if (!response.ok) {
throw new Error(`API request failed with status ${response.status}`);
}

const data = await response.json();
const hint = data.choices[0].message.content;

hintText.innerText = hint;
hintContainer.classList.remove('hidden');
errorContainer.classList.add('hidden');

// Deduct time
chrome.runtime.sendMessage({ type: 'deductTime', cost: cost[level] });
loadTimerFromStorage();
} catch (error) {
showError(error.message);
}
async function fetchHint(apiKey, model, level, context) {
const cost = {
ordinary: 2,
advanced: 3,
expert: 5,
};

try {
// Use the global window.generateHint() from prompts.js
const hint = await window.generateHint(apiKey, model, level, context);

hintText.innerText = hint;
hintContainer.classList.remove('hidden');
errorContainer.classList.add('hidden');

// Deduct time
chrome.runtime.sendMessage({ type: 'deductTime', cost: cost[level] });
loadTimerFromStorage();
} catch (error) {
showError(error.message);
}

}
function showError(message) {
errorText.innerText = message;
errorContainer.classList.remove('hidden');
Expand Down Expand Up @@ -333,4 +306,3 @@ function loadTimerFromStorage() {
updateTimerDisplay(timeAllowed);
});
}

141 changes: 141 additions & 0 deletions prompts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
(() => {
'use strict';

const HINT_PROMPTS = {
ordinary: {
systemPrompt: `You are a helpful LeetCode tutor providing ORDINARY level hints.

Your goal: Give a gentle nudge in the right direction without revealing too much.
Rules:
- FIRST LINE must be exactly: "Here's an Ordinary level hint..."
- Do NOT write anything before that first line.
- Focus on understanding the problem better.
- Suggest what data structures or concepts to consider.
- Ask guiding questions that help the student think.
- Keep it simple, concise, and encouraging.
- Do NOT provide code, pseudo-code, or partial algorithms.
- Stop immediately when you reach 50 words. Do NOT exceed 50 words.
- STRICTLY respond in no more than 50 words. The first line counts as part of the 50 words.
- Do not add filler or background text before or after the hint.`,
temperature: 0.3,
maxWords: 50
},

advanced: {
systemPrompt: `You are an experienced LeetCode mentor providing ADVANCED level hints.

Your goal: Provide deeper and more specific guidance without fully solving the problem.
Rules:
- FIRST LINE must be exactly: "Here's an Advanced level hint..."
- Do NOT write anything before that first line.
- Explain the general approach or recognized problem pattern.
- Mention specific algorithms or techniques (e.g., "two pointers", "DFS with memoization") without giving full implementation.
- Discuss time/space complexity considerations.
- Suggest how to break the problem into manageable steps.
- Avoid giving complete pseudo-code or direct answers.
- Stop immediately when you reach 75 words. Do NOT exceed 75 words.
- STRICTLY respond in no more than 75 words. The first line counts as part of the 75 words.
- Do not add filler or background text before or after the hint.`,
temperature: 0.3,
maxWords: 75
},

expert: {
systemPrompt: `You are a LeetCode expert providing EXPERT level hints.

Your goal: Give comprehensive strategic guidance for optimal problem-solving.
Rules:
- FIRST LINE must be exactly: "Here's an Expert level hint..."
- Do NOT write anything before that first line.
- Explain the optimal algorithm and why it works.
- Discuss alternative approaches and trade-offs.
- Analyze time and space complexity in detail.
- Mention important edge cases.
- Provide a high-level roadmap for the implementation.
- Let the student write the actual code.
- Stop immediately when you reach 100 words. Do NOT exceed 100 words.
- STRICTLY respond in no more than 100 words. The first line counts as part of the 100 words.
- Do not add filler or background text before or after the hint.`,
temperature: 0.3,
maxWords: 100
}
};


function filterResponse(text, maxWords) {
if (!text || typeof text !== 'string') return '';

let cleaned = text.trimStart();
cleaned = cleaned.replace(/<think>[\s\S]*?<\/think>/gi, '');
cleaned = cleaned.replace(/<reflection>[\s\S]*?<\/reflection>/gi, '');
cleaned = cleaned.replace(/<[^>]+>/g, '');
cleaned = cleaned.replace(/^(Assistant:|AI:|System:)\s*/i, '');

const match = cleaned.match(/(Here's an (Ordinary|Advanced|Expert) level hint.*)/i);
if (match) {
cleaned = match[1] + ' ' + cleaned.slice(match.index + match[0].length).trim();
}
cleaned = cleaned.replace(/\n\s*\n\s*\n+/g, '\n\n').trim();

return cleaned;
}

function extractModelText(responseJson) {
try {
if (!responseJson) return '';

if (responseJson.choices && responseJson.choices[0] && responseJson.choices[0].message && typeof responseJson.choices[0].message.content === 'string') {
return responseJson.choices[0].message.content;
}

if (responseJson.generations && responseJson.generations[0] && responseJson.generations[0][0] && responseJson.generations[0][0].text) {
return responseJson.generations[0][0].text;
}

return JSON.stringify(responseJson).slice(0, 1000);
} catch (e) {
return '';
}
}

window.generateHint = async function (apiKey, model, level, problemContext) {
if (!apiKey) throw new Error('Groq API key is required.');
if (!model) throw new Error('Model name is required.');
if (!HINT_PROMPTS[level]) throw new Error('Invalid hint level.');

const promptConfig = HINT_PROMPTS[level];

const systemMessage = promptConfig.systemPrompt;
const userMessage = `Problem Context:\n${problemContext}\n\nPlease provide a ${level.toUpperCase()}-level hint for this problem. Keep within ${promptConfig.maxWords} words. Do NOT provide code or full solution.`;

const requestBody = {
model: model,
messages: [
{ role: 'system', content: systemMessage },
{ role: 'user', content: userMessage }
],
temperature: promptConfig.temperature,
max_tokens: Math.ceil(promptConfig.maxWords * 1.6)
};

const resp = await fetch('https://api.groq.com/openai/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify(requestBody)
});

if (!resp.ok) {
const errText = await resp.text().catch(() => '');
throw new Error(`Groq API error ${resp.status}: ${errText || resp.statusText}`);
}

const data = await resp.json();
let raw = extractModelText(data);
raw = filterResponse(raw, promptConfig.maxWords);
return raw;
};

})();