- WXT + React 18 + TypeScript setup - Background service worker (LLM API proxy) - Content script (floating toolbar + text selection) - Options page (provider/model/API key config) - Popup UI - Gitea Actions workflows (CI, preview, release) - Vitest unit tests + Playwright E2E setup - Supports: OpenAI, Anthropic, Groq, OpenRouter
106 lines
3.7 KiB
TypeScript
106 lines
3.7 KiB
TypeScript
import { defineBackground } from 'wxt/sandbox';
|
|
|
|
export default defineBackground(() => {
|
|
console.log('LexAI background service worker started');
|
|
|
|
// Handle messages from content scripts
|
|
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
if (message.type === 'ANALYZE_TEXT') {
|
|
handleAnalyzeText(message.payload).then(sendResponse);
|
|
return true; // Keep channel open for async response
|
|
}
|
|
});
|
|
});
|
|
|
|
async function handleAnalyzeText(payload: { text: string; action: string }) {
|
|
try {
|
|
const config = await chrome.storage.local.get(['provider', 'apiKey', 'model']);
|
|
|
|
if (!config.apiKey) {
|
|
return { error: 'No API key configured. Please open LexAI settings.' };
|
|
}
|
|
|
|
// Route to correct provider
|
|
switch (config.provider) {
|
|
case 'openai':
|
|
return await callOpenAI(payload, config);
|
|
case 'anthropic':
|
|
return await callAnthropic(payload, config);
|
|
case 'groq':
|
|
return await callGroq(payload, config);
|
|
default:
|
|
return { error: 'Unknown provider. Please check settings.' };
|
|
}
|
|
} catch (err) {
|
|
return { error: String(err) };
|
|
}
|
|
}
|
|
|
|
async function callOpenAI(payload: { text: string; action: string }, config: any) {
|
|
const res = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${config.apiKey}`,
|
|
},
|
|
body: JSON.stringify({
|
|
model: config.model || 'gpt-4o-mini',
|
|
messages: [
|
|
{ role: 'system', content: getSystemPrompt(payload.action) },
|
|
{ role: 'user', content: payload.text },
|
|
],
|
|
}),
|
|
});
|
|
const data = await res.json();
|
|
return { result: data.choices?.[0]?.message?.content };
|
|
}
|
|
|
|
async function callAnthropic(payload: { text: string; action: string }, config: any) {
|
|
const res = await fetch('https://api.anthropic.com/v1/messages', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'x-api-key': config.apiKey,
|
|
'anthropic-version': '2023-06-01',
|
|
},
|
|
body: JSON.stringify({
|
|
model: config.model || 'claude-3-5-haiku-20241022',
|
|
max_tokens: 1024,
|
|
system: getSystemPrompt(payload.action),
|
|
messages: [{ role: 'user', content: payload.text }],
|
|
}),
|
|
});
|
|
const data = await res.json();
|
|
return { result: data.content?.[0]?.text };
|
|
}
|
|
|
|
async function callGroq(payload: { text: string; action: string }, config: any) {
|
|
const res = await fetch('https://api.groq.com/openai/v1/chat/completions', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${config.apiKey}`,
|
|
},
|
|
body: JSON.stringify({
|
|
model: config.model || 'llama-3.3-70b-versatile',
|
|
messages: [
|
|
{ role: 'system', content: getSystemPrompt(payload.action) },
|
|
{ role: 'user', content: payload.text },
|
|
],
|
|
}),
|
|
});
|
|
const data = await res.json();
|
|
return { result: data.choices?.[0]?.message?.content };
|
|
}
|
|
|
|
function getSystemPrompt(action: string): string {
|
|
const prompts: Record<string, string> = {
|
|
grammar: 'You are a grammar checker. Fix grammar, spelling, and punctuation errors in the text. Return only the corrected text without explanations.',
|
|
rephrase: 'You are a writing assistant. Rephrase the given text to be clearer and more professional. Return only the rephrased text.',
|
|
tone: 'You are a writing coach. Analyze the tone of the text and suggest improvements. Be concise.',
|
|
shorten: 'You are an editor. Shorten the text while keeping the key message. Return only the shortened version.',
|
|
expand: 'You are a writer. Expand the given text with more detail and context. Return only the expanded version.',
|
|
};
|
|
return prompts[action] || prompts.grammar;
|
|
}
|