-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathbackground.js
More file actions
217 lines (190 loc) · 6.26 KB
/
Copy pathbackground.js
File metadata and controls
217 lines (190 loc) · 6.26 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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
// AI Auto Reply - Background Service Worker
const GEMINI_API_URL =
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent";
/**
* Listen for messages from content scripts
*/
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === "GET_AI_REPLY") {
handleAIReply(message.text, message.platform, message.history || [])
.then((response) => sendResponse(response))
.catch((error) => sendResponse({ success: false, error: error.message }));
return true; // Keep message channel open for async response
}
if (message.type === "SEND_WEBHOOK") {
handleWebhook(message.data)
.then(() => sendResponse({ success: true }))
.catch((error) => sendResponse({ success: false, error: error.message }));
return true;
}
});
/**
* Generate AI reply using Google Gemini API
*/
async function handleAIReply(messageText, platform, history) {
console.log(
`[AI Auto Reply] Generating reply for ${platform}: "${messageText.substring(0, 50)}..."`,
);
console.log(
`[AI Auto Reply] Conversation history: ${history.length} messages`,
);
// Load settings
const settings = await getSettings();
if (!settings.apiKey) {
throw new Error(
"Gemini API key not configured. Please set it in the extension popup.",
);
}
// Check keyword rules first
const keywordReply = checkKeywordRules(messageText, settings.keywordRules);
if (keywordReply) {
console.log("[AI Auto Reply] Keyword rule matched, using canned reply");
return { success: true, reply: keywordReply, source: "keyword" };
}
// Build the prompt with conversation context
const systemPrompt =
settings.systemPrompt ||
"You are a helpful customer support assistant. Reply politely and briefly to the following customer message.";
// Build the Gemini request with full conversation context
let promptText = "";
if (history.length > 0) {
promptText += `ROLE: ${systemPrompt}\n\n`;
promptText += `CONVERSATION HISTORY (most recent messages, in order):\n`;
promptText += `────────────────────────────────────\n`;
for (const msg of history) {
const label = msg.role === "customer" ? "👤 Customer" : "🤖 You";
promptText += `${label}: ${msg.text}\n`;
}
promptText += `────────────────────────────────────\n\n`;
promptText += `TASK: The customer's latest message is: "${messageText}"\n`;
promptText += `Write a natural reply based on the full conversation above.\n\n`;
promptText += `RULES:\n`;
promptText += `- Reply in the SAME LANGUAGE the customer is using\n`;
promptText += `- Keep it brief and conversational (1-3 sentences)\n`;
promptText += `- Be contextually relevant to what was discussed\n`;
promptText += `- Do NOT include any label, prefix, or emoji at the start\n`;
promptText += `- Just output the reply text, nothing else`;
} else {
promptText += `${systemPrompt}\n\n`;
promptText += `Customer message: "${messageText}"\n\n`;
promptText += `Reply briefly and naturally in the same language the customer is using. Just output the reply text, no labels or prefixes.`;
}
const requestBody = {
contents: [
{
role: "user",
parts: [{ text: promptText }],
},
],
generationConfig: {
temperature: 0.7,
maxOutputTokens: 256,
topP: 0.9,
},
};
try {
console.log("[AI Auto Reply] Calling Gemini API...");
const response = await fetch(GEMINI_API_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-goog-api-key": settings.apiKey,
},
body: JSON.stringify(requestBody),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
const errorMsg =
errorData?.error?.message || `API returned status ${response.status}`;
throw new Error(errorMsg);
}
const data = await response.json();
const replyText = data?.candidates?.[0]?.content?.parts?.[0]?.text;
if (!replyText) {
throw new Error("No reply generated by Gemini API");
}
const cleanReply = replyText.trim();
console.log(
`[AI Auto Reply] Generated reply: "${cleanReply.substring(0, 50)}..."`,
);
return { success: true, reply: cleanReply, source: "gemini" };
} catch (fetchError) {
console.error(
"[AI Auto Reply] Gemini API fetch error:",
fetchError.message,
);
console.error(
"[AI Auto Reply] This may be caused by: invalid API key, network issue, or missing host_permissions in manifest.json",
);
throw new Error(`Gemini API fetch failed: ${fetchError.message}`);
}
}
/**
* Check if message matches any keyword rules
*/
function checkKeywordRules(messageText, keywordRules) {
if (!keywordRules || keywordRules.length === 0) return null;
const lowerMessage = messageText.toLowerCase();
for (const rule of keywordRules) {
if (rule.keyword && rule.reply) {
if (lowerMessage.includes(rule.keyword.toLowerCase())) {
return rule.reply;
}
}
}
return null;
}
/**
* Send conversation data to CRM webhook
*/
async function handleWebhook(data) {
const settings = await getSettings();
if (!settings.webhookUrl) {
console.log("[AI Auto Reply] No webhook URL configured, skipping");
return;
}
console.log("[AI Auto Reply] Sending webhook to:", settings.webhookUrl);
try {
const response = await fetch(settings.webhookUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
timestamp: new Date().toISOString(),
platform: data.platform,
incomingMessage: data.incomingMessage,
aiReply: data.aiReply,
replySource: data.replySource,
}),
});
if (!response.ok) {
console.error(
`[AI Auto Reply] Webhook failed with status ${response.status}`,
);
} else {
console.log("[AI Auto Reply] Webhook sent successfully");
}
} catch (error) {
console.error("[AI Auto Reply] Webhook error:", error.message);
}
}
/**
* Get settings from chrome.storage.local
*/
function getSettings() {
return new Promise((resolve) => {
chrome.storage.local.get(
{
enabled: false,
apiKey: "",
systemPrompt:
"You are a helpful customer support assistant. Reply politely and briefly to the following customer message.",
replyDelay: 3,
keywordRules: [],
webhookUrl: "",
},
resolve,
);
});
}