Add YT assistant chrome extension (#2485)
This commit is contained in:
255
examples/yt-assistant-chrome/src/background.js
Normal file
255
examples/yt-assistant-chrome/src/background.js
Normal file
@@ -0,0 +1,255 @@
|
||||
// Background script to handle API calls to OpenAI and manage extension state
|
||||
|
||||
// Configuration (will be stored in sync storage eventually)
|
||||
let config = {
|
||||
apiKey: "", // Will be set by user in options
|
||||
mem0ApiKey: "", // Will be set by user in options
|
||||
model: "gpt-4",
|
||||
maxTokens: 2000,
|
||||
temperature: 0.7,
|
||||
enabledSites: ["youtube.com"],
|
||||
};
|
||||
|
||||
// Track if config is loaded
|
||||
let isConfigLoaded = false;
|
||||
|
||||
// Initialize configuration from storage
|
||||
chrome.storage.sync.get(
|
||||
["apiKey", "mem0ApiKey", "model", "maxTokens", "temperature", "enabledSites"],
|
||||
(result) => {
|
||||
if (result.apiKey) config.apiKey = result.apiKey;
|
||||
if (result.mem0ApiKey) config.mem0ApiKey = result.mem0ApiKey;
|
||||
if (result.model) config.model = result.model;
|
||||
if (result.maxTokens) config.maxTokens = result.maxTokens;
|
||||
if (result.temperature) config.temperature = result.temperature;
|
||||
if (result.enabledSites) config.enabledSites = result.enabledSites;
|
||||
|
||||
isConfigLoaded = true;
|
||||
}
|
||||
);
|
||||
|
||||
// Listen for messages from content script or popup
|
||||
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||
// Handle different message types
|
||||
switch (request.action) {
|
||||
case "sendChatRequest":
|
||||
sendChatRequest(request.messages, request.model || config.model)
|
||||
.then((response) => sendResponse(response))
|
||||
.catch((error) => sendResponse({ error: error.message }));
|
||||
return true; // Required for async response
|
||||
|
||||
case "saveConfig":
|
||||
saveConfig(request.config)
|
||||
.then(() => sendResponse({ success: true }))
|
||||
.catch((error) => sendResponse({ error: error.message }));
|
||||
return true;
|
||||
|
||||
case "getConfig":
|
||||
// If config isn't loaded yet, load it first
|
||||
if (!isConfigLoaded) {
|
||||
chrome.storage.sync.get(
|
||||
[
|
||||
"apiKey",
|
||||
"mem0ApiKey",
|
||||
"model",
|
||||
"maxTokens",
|
||||
"temperature",
|
||||
"enabledSites",
|
||||
],
|
||||
(result) => {
|
||||
if (result.apiKey) config.apiKey = result.apiKey;
|
||||
if (result.mem0ApiKey) config.mem0ApiKey = result.mem0ApiKey;
|
||||
if (result.model) config.model = result.model;
|
||||
if (result.maxTokens) config.maxTokens = result.maxTokens;
|
||||
if (result.temperature) config.temperature = result.temperature;
|
||||
if (result.enabledSites) config.enabledSites = result.enabledSites;
|
||||
isConfigLoaded = true;
|
||||
sendResponse({ config });
|
||||
}
|
||||
);
|
||||
return true;
|
||||
}
|
||||
sendResponse({ config });
|
||||
return false;
|
||||
|
||||
case "openOptions":
|
||||
// Open options page
|
||||
chrome.runtime.openOptionsPage(() => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.error(
|
||||
"Error opening options page:",
|
||||
chrome.runtime.lastError
|
||||
);
|
||||
// Fallback: Try to open directly in a new tab
|
||||
chrome.tabs.create({ url: chrome.runtime.getURL("options.html") });
|
||||
}
|
||||
sendResponse({ success: true });
|
||||
});
|
||||
return true;
|
||||
|
||||
case "toggleChat":
|
||||
// Forward the toggle request to the active tab
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
||||
if (tabs[0]) {
|
||||
chrome.tabs
|
||||
.sendMessage(tabs[0].id, { action: "toggleChat" })
|
||||
.then((response) => sendResponse(response))
|
||||
.catch((error) => sendResponse({ error: error.message }));
|
||||
} else {
|
||||
sendResponse({ error: "No active tab found" });
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// Handle extension icon click - toggle chat visibility
|
||||
chrome.action.onClicked.addListener((tab) => {
|
||||
chrome.tabs
|
||||
.sendMessage(tab.id, { action: "toggleChat" })
|
||||
.catch((error) => console.error("Error toggling chat:", error));
|
||||
});
|
||||
|
||||
// Save configuration to sync storage
|
||||
async function saveConfig(newConfig) {
|
||||
// Validate API key if provided
|
||||
if (newConfig.apiKey) {
|
||||
try {
|
||||
const isValid = await validateApiKey(newConfig.apiKey);
|
||||
if (!isValid) {
|
||||
throw new Error("Invalid API key");
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(`API key validation failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Update local config
|
||||
config = { ...config, ...newConfig };
|
||||
|
||||
// Save to sync storage
|
||||
return chrome.storage.sync.set(newConfig);
|
||||
}
|
||||
|
||||
// Validate OpenAI API key with a simple request
|
||||
async function validateApiKey(apiKey) {
|
||||
try {
|
||||
const response = await fetch("https://api.openai.com/v1/models", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API returned ${response.status}`);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("API key validation error:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Send a chat request to OpenAI API
|
||||
async function sendChatRequest(messages, model) {
|
||||
// Check if API key is set
|
||||
if (!config.apiKey) {
|
||||
return {
|
||||
error:
|
||||
"API key not configured. Please set your OpenAI API key in the extension options.",
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch("https://api.openai.com/v1/chat/completions", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${config.apiKey}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: model || config.model,
|
||||
messages: messages.map((msg) => ({
|
||||
role: msg.role,
|
||||
content: msg.content,
|
||||
})),
|
||||
max_tokens: config.maxTokens,
|
||||
temperature: config.temperature,
|
||||
stream: true, // Enable streaming
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(
|
||||
errorData.error?.message || `API returned ${response.status}`
|
||||
);
|
||||
}
|
||||
|
||||
// Create a ReadableStream from the response
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = "";
|
||||
|
||||
// Process the stream
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
// Decode the chunk and add to buffer
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
|
||||
// Process complete lines
|
||||
const lines = buffer.split("\n");
|
||||
buffer = lines.pop() || ""; // Keep the last incomplete line in the buffer
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith("data: ")) {
|
||||
const data = line.slice(6);
|
||||
if (data === "[DONE]") {
|
||||
// Stream complete
|
||||
return { done: true };
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
if (parsed.choices[0].delta.content) {
|
||||
// Send the chunk to the content script
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
||||
if (tabs[0]) {
|
||||
chrome.tabs.sendMessage(tabs[0].id, {
|
||||
action: "streamChunk",
|
||||
chunk: parsed.choices[0].delta.content,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error parsing chunk:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { done: true };
|
||||
} catch (error) {
|
||||
console.error("Error sending chat request:", error);
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// Future: Add mem0 integration functions here
|
||||
// When ready, replace with actual implementation
|
||||
function mem0Integration() {
|
||||
// Placeholder for future mem0 integration
|
||||
return {
|
||||
getUserMemories: async (userId) => {
|
||||
return { memories: [] };
|
||||
},
|
||||
saveMemory: async (userId, memory) => {
|
||||
return { success: true };
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user