-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbackground.js
More file actions
181 lines (148 loc) · 7.35 KB
/
background.js
File metadata and controls
181 lines (148 loc) · 7.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
console.log("aIrrange service worker initialized. Ready to receive messages.");
async function getKeywordsFromGemini(query) {
console.log("Step 1: Attempting to get keywords from Gemini.");
// Fetch the user's saved API key from storage.
const data = await chrome.storage.local.get('aIrrange_geminiApiKey');
const apiKey = data.aIrrange_geminiApiKey;
// If no key is found, log a warning and return empty to trigger the fallback.
if (!apiKey) {
console.warn(" -> Warning: No Gemini API key found in storage. Aborting API call.");
return [];
}
console.log(" -> API key found. Preparing API call.");
const GEMINI_API_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key=${apiKey}`;
const prompt = `From the following user prompt, extract up to 10 of the most important and relevant keywords that summarize the core topic. Return them as a comma-separated list.\n\nUser Prompt: "${query}"\n\nKeywords:`;
try {
console.log(" -> Sending request to Gemini API...");
const response = await fetch(GEMINI_API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] })
});
if (!response.ok) {
// Log the error response from the server for better debugging
const errorBody = await response.text();
throw new Error(`API call failed with status: ${response.status}. Body: ${errorBody}`);
}
const responseData = await response.json();
// It's good practice to log the entire response object to inspect its structure
// console.log(" -> Full Gemini API response:", JSON.stringify(responseData, null, 2));
const keywordsText = responseData.candidates[0].content.parts[0].text;
console.log(" -> Success! Gemini returned keywords:", keywordsText);
return keywordsText.split(',').map(kw => kw.trim()).filter(Boolean);
} catch (error) {
console.error(" -> Error calling Gemini API:", error);
return []; // Return empty on API failure to trigger fallback
}
}
// The simple extractor fallback.
const extractKeywordsSimple = (query) => {
console.log("Step 1b: Falling back to simple keyword extraction.");
const words = query.toLowerCase().match(/\b\w{3,}\b/g) || [];
return new Set(words);
};
// The main function to save the data.
async function saveSearch(query, url) {
console.log("\n--- New Save Operation ---");
console.log(`Received query: "${query}" for URL: ${url}`);
let finalKeywords = await getKeywordsFromGemini(query);
if (finalKeywords.length === 0) {
console.log(" -> AI extraction failed or was skipped. Using simple extractor as fallback.");
finalKeywords = Array.from(extractKeywordsSimple(query));
}
console.log("Step 2: Preparing to save final keywords:", finalKeywords);
chrome.storage.local.get({ conversations: [] }, (result) => {
let conversations = result.conversations;
const existingConvo = conversations.find(convo => convo.url === url);
if (existingConvo) {
console.log(" -> Found existing conversation. Merging keywords.");
const existingKeywordsSet = new Set(existingConvo.keywords);
finalKeywords.forEach(keyword => existingKeywordsSet.add(keyword));
existingConvo.keywords = Array.from(existingKeywordsSet).sort();
} else {
console.log(" -> This is a new conversation. Creating new entry.");
const newConversation = { url: url, keywords: Array.from(new Set(finalKeywords)).sort() };
conversations.unshift(newConversation);
}
// Save the updated array back to storage.
chrome.storage.local.set({ conversations: conversations }, () => {
console.log("Step 3: Save complete! Current storage state:", conversations);
console.log("--- End Save Operation ---\n");
});
});
};
async function findBestMatchWithGemini(searchQuery, candidates) {
console.log("background.js: AI search ranking initiated.");
console.log(` -> Search Query: "${searchQuery}"`);
console.log(" -> Candidates to Rank:", candidates);
const data = await chrome.storage.local.get('aIrrange_geminiApiKey');
const apiKey = data.aIrrange_geminiApiKey;
if (!apiKey) {
console.warn(" -> No API key found. Aborting AI ranking.");
return null;
}
const prompt = `A user is searching for a conversation with the query: "${searchQuery}".
I have pre-filtered a list of potential conversations based on keywords. Each conversation has a URL and its associated keywords.
Your task is to analyze this list and identify the single best match. Return ONLY the URL of the best matching conversation and nothing else.
Here is the list of candidates in JSON format:
${JSON.stringify(candidates, null, 2)}
Best matching URL:`;
console.log(" -> Constructed prompt for Gemini API:", prompt);
const GEMINI_API_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key=${apiKey}`;
try {
console.log(" -> Sending request to Gemini API...");
const response = await fetch(GEMINI_API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] })
});
if (!response.ok) {
const errorBody = await response.text();
throw new Error(`API call failed with status: ${response.status}. Body: ${errorBody}`);
}
const responseData = await response.json();
console.log(" -> Full Gemini API response:", responseData);
const bestUrl = responseData.candidates[0].content.parts[0].text.trim();
console.log(` -> Success! Gemini identified best URL: ${bestUrl}`);
return bestUrl;
} catch (error) {
console.error(" -> Error during AI search ranking:", error);
return null;
}
};
// The message listener that kicks everything off.
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'newSearch') {
const { query, url } = message.payload;
// Normalize the URL to remove query parameters and fragments.
let conversationUrl = url.split('?')[0].split('#')[0];
// Further normalize for specific ChatGPT URL structure.
if (url.includes('/c/')) {
const urlParts = url.split('/c/');
const conversationId = urlParts[1].split('/')[0];
conversationUrl = `${urlParts[0]}/c/${conversationId}`;
}
saveSearch(query, conversationUrl);
return true; // Indicates you will send a response asynchronously.
}
if (message.type === 'getAllConversations') {
console.log("Request received from all.html to get all conversations.");
chrome.storage.local.get({ conversations: [] }, (result) => {
console.log("Sending conversations back to all.html:", result.conversations);
sendResponse({ data: result.conversations });
});
// *** THIS IS THE CRUCIAL FIX ***
// You MUST return true here as well to signal that you will be
// sending a response asynchronously.
return true;
}
if (message.type === 'performAiSearch') {
console.log("background.js: Received 'performAiSearch' message.");
const { query, candidates } = message.payload;
findBestMatchWithGemini(query, candidates).then(bestMatchUrl => {
console.log("background.js: Sending best match URL back to all.js:", bestMatchUrl);
sendResponse({ bestMatchUrl: bestMatchUrl });
});
return true; // Crucial for async response
}
});