1- import 'webextension-polyfill' ;
2- import { GoogleGenAI , Type } from '@google/genai' ;
1+ import "webextension-polyfill" ;
2+ import { GoogleGenAI , Type } from "@google/genai" ;
3+ import OpenAI from "openai" ;
34
45// Temporary storage for autofill requests, as service workers are stateless
5- const autofillRequests : Record < number , { profile : any ; apiKey : string ; selectedAiModel : string } > = { } ; // Change type to 'any' for structured profile
6+ const autofillRequests : Record <
7+ number ,
8+ {
9+ profile : any ;
10+ geminiApiKey : string ;
11+ selectedGeminiModel : string ;
12+ openAiApiKey : string ;
13+ selectedOpenAiModel : string ;
14+ selectedProvider : "gemini" | "openai" ;
15+ }
16+ > = { } ; // Change type to 'any' for structured profile
617
718chrome . runtime . onMessage . addListener ( ( message , sender , sendResponse ) => {
819 // Mark sendResponse as asynchronous to allow handlers to use it later
@@ -27,10 +38,19 @@ async function handleAutofillRequest(payload: { tabId: number; profile: any; api
2738 return ;
2839 }
2940
30- const { selectedAiModel } = await chrome . storage . local . get ( 'selectedAiModel' ) ;
41+ const { model : selectedGeminiModel } = await chrome . storage . local . get ( 'ai-model-storage-key' ) ;
42+ const { apiKey : openAiApiKey , model : selectedOpenAiModel } = await chrome . storage . local . get ( 'openai-storage-key' ) ;
43+ const { provider : selectedProvider } = await chrome . storage . local . get ( 'provider-storage-key' ) ;
3144
3245 // Store the payload temporarily
33- autofillRequests [ tabId ] = { profile, apiKey, selectedAiModel : selectedAiModel || 'gemini-2.5-flash-lite-preview-06-17' } ;
46+ autofillRequests [ tabId ] = {
47+ profile,
48+ geminiApiKey : apiKey ,
49+ selectedGeminiModel : selectedGeminiModel || "gemini-2.5-flash-lite-preview-06-17" ,
50+ openAiApiKey : openAiApiKey || "" ,
51+ selectedOpenAiModel : selectedOpenAiModel || "gpt-4.1-mini" ,
52+ selectedProvider : selectedProvider || "gemini"
53+ } ;
3454
3555 try {
3656 // Send message to content script to extract form data
@@ -54,23 +74,59 @@ async function handleFormDataExtracted(pageContextItems: any[], tabId: number |
5474 return ;
5575 }
5676
57- const { profile, apiKey, selectedAiModel } = autofillRequests [ tabId ] ;
77+ const {
78+ profile,
79+ geminiApiKey,
80+ selectedGeminiModel,
81+ openAiApiKey,
82+ selectedOpenAiModel,
83+ selectedProvider
84+ } = autofillRequests [ tabId ] ;
85+
86+ let aiModel : any ;
87+ let currentApiKey : string ;
88+ let modelName : string ;
5889
59- if ( ! apiKey ) {
60- console . error ( 'Gemini API Key is missing.' ) ;
61- chrome . runtime . sendMessage ( { type : 'UPDATE_POPUP_STATUS' , payload : 'Error: Gemini API Key is missing. Please set it in the popup.' } ) ;
90+ if ( selectedProvider === "gemini" ) {
91+ if ( ! geminiApiKey ) {
92+ console . error ( "Gemini API Key is missing." ) ;
93+ chrome . runtime . sendMessage ( {
94+ type : "UPDATE_POPUP_STATUS" ,
95+ payload : "Error: Gemini API Key is missing. Please set it in the popup."
96+ } ) ;
97+ delete autofillRequests [ tabId ] ;
98+ sendResponse ( { success : false , error : "Gemini API Key is missing." } ) ;
99+ return ;
100+ }
101+ aiModel = new GoogleGenAI ( { apiKey : geminiApiKey } ) ;
102+ modelName = selectedGeminiModel ;
103+ currentApiKey = geminiApiKey ;
104+ } else if ( selectedProvider === "openai" ) {
105+ if ( ! openAiApiKey ) {
106+ console . error ( "OpenAI API Key is missing." ) ;
107+ chrome . runtime . sendMessage ( {
108+ type : "UPDATE_POPUP_STATUS" ,
109+ payload : "Error: OpenAI API Key is missing. Please set it in the popup."
110+ } ) ;
111+ delete autofillRequests [ tabId ] ;
112+ sendResponse ( { success : false , error : "OpenAI API Key is missing." } ) ;
113+ return ;
114+ }
115+ aiModel = new OpenAI ( { apiKey : openAiApiKey } ) ;
116+ modelName = selectedOpenAiModel ;
117+ currentApiKey = openAiApiKey ;
118+ } else {
119+ console . error ( "No AI provider selected." ) ;
120+ chrome . runtime . sendMessage ( { type : "UPDATE_POPUP_STATUS" , payload : "Error: No AI provider selected." } ) ;
62121 delete autofillRequests [ tabId ] ;
63- sendResponse ( { success : false , error : 'Gemini API Key is missing.' } ) ; // Send error response
122+ sendResponse ( { success : false , error : "No AI provider selected." } ) ;
64123 return ;
65124 }
66125
67126 // Acknowledge receipt to the content script immediately.
68- // The actual result of the Gemini call will be communicated via chrome.tabs.sendMessage later.
69- sendResponse ( { success : true , message : 'Form data received, processing with Gemini...' } ) ;
70- chrome . runtime . sendMessage ( { type : 'UPDATE_POPUP_STATUS' , payload : 'Sending data to Gemini...' } ) ;
71-
72- const ai = new GoogleGenAI ( { apiKey : apiKey } ) ;
73- const model = ai . models ;
127+ // The actual result of the AI call will be communicated via chrome.tabs.sendMessage later.
128+ sendResponse ( { success : true , message : `Form data received, processing with ${ selectedProvider } ...` } ) ;
129+ chrome . runtime . sendMessage ( { type : "UPDATE_POPUP_STATUS" , payload : `Sending data to ${ selectedProvider } ...` } ) ;
74130
75131 // Sort page context items by their DOM order
76132 pageContextItems . sort ( ( a , b ) => a . domOrder - b . domOrder ) ;
@@ -104,75 +160,135 @@ async function handleFormDataExtracted(pageContextItems: any[], tabId: number |
104160 const prompt = `You are an AI assistant specialized in intelligently filling web forms.\nHere is a description of the web page's structure, including text content and form elements, ordered by their appearance in the DOM:\n${ pageStructureDescription } \n\nHere is a more structured list of the form elements found on the page, including their unique selectors for interaction:\n${ JSON . stringify ( formElementsForPrompt , null , 2 ) } \n\nHere is the user's personal information. Use these details to fill the form:\n${ profile } \n\nYour goal is to fill out this form accurately using the provided user information.\nYou have the following tools available:\nfunction fill_text_input(selector: string, value: string, field_type: string)\nfunction select_dropdown_option(selector: string, value: string)\nfunction check_radio_or_checkbox(selector: string, checked: boolean)\n\nBased on the form elements, the surrounding text context, and user data, suggest the next action(s) to take using the available tools. Output your action(s) as a JSON array of tool calls.\n` ;
105161
106162 try {
107- const result = await model . generateContent ( {
108- model : selectedAiModel ,
109- contents : [ { role : 'user' , parts : [ { text : prompt } ] } ] ,
110- config : {
111- tools : [
112- {
113- functionDeclarations : [
114- {
115- name : 'fill_text_input' ,
116- parameters : {
117- type : Type . OBJECT ,
118- properties : {
119- selector : { type : Type . STRING } ,
120- value : { type : Type . STRING } ,
121- field_type : { type : Type . STRING } ,
122- } ,
123- required : [ 'selector' , 'value' , 'field_type' ] ,
163+ let toolCalls ;
164+ if ( selectedProvider === "gemini" ) {
165+ const geminiResult = await aiModel . models . generateContent ( {
166+ model : modelName ,
167+ contents : [ { role : "user" , parts : [ { text : prompt } ] } ] ,
168+ config : {
169+ tools : [
170+ {
171+ functionDeclarations : [
172+ {
173+ name : "fill_text_input" ,
174+ parameters : {
175+ type : Type . OBJECT ,
176+ properties : {
177+ selector : { type : Type . STRING } ,
178+ value : { type : Type . STRING } ,
179+ field_type : { type : Type . STRING }
180+ } ,
181+ required : [ "selector" , "value" , "field_type" ]
182+ }
124183 } ,
125- } ,
126- {
127- name : 'select_dropdown_option' ,
128- parameters : {
129- type : Type . OBJECT ,
130- properties : {
131- selector : { type : Type . STRING } ,
132- value : { type : Type . STRING } ,
133- } ,
134- required : [ 'selector' , 'value' ] ,
184+ {
185+ name : "select_dropdown_option" ,
186+ parameters : {
187+ type : Type . OBJECT ,
188+ properties : {
189+ selector : { type : Type . STRING } ,
190+ value : { type : Type . STRING }
191+ } ,
192+ required : [ "selector" , "value" ]
193+ }
135194 } ,
195+ {
196+ name : "check_radio_or_checkbox" ,
197+ parameters : {
198+ type : Type . OBJECT ,
199+ properties : {
200+ selector : { type : Type . STRING } ,
201+ checked : { type : Type . BOOLEAN }
202+ } ,
203+ required : [ "selector" , "checked" ]
204+ }
205+ }
206+ ]
207+ }
208+ ]
209+ }
210+ } ) ;
211+ toolCalls = geminiResult . functionCalls ;
212+ console . log ( `${ selectedProvider } LLM Raw Response Text:` , geminiResult . text ) ;
213+ } else if ( selectedProvider === "openai" ) {
214+ const tools = [
215+ {
216+ type : "function" ,
217+ function : {
218+ name : "fill_text_input" ,
219+ parameters : {
220+ type : "object" ,
221+ properties : {
222+ selector : { type : "string" } ,
223+ value : { type : "string" } ,
224+ field_type : { type : "string" }
136225 } ,
137- {
138- name : 'check_radio_or_checkbox' ,
139- parameters : {
140- type : Type . OBJECT ,
141- properties : {
142- selector : { type : Type . STRING } ,
143- checked : { type : Type . BOOLEAN } ,
144- } ,
145- required : [ 'selector' , 'checked' ] ,
146- } ,
226+ required : [ "selector" , "value" , "field_type" ]
227+ }
228+ }
229+ } ,
230+ {
231+ type : "function" ,
232+ function : {
233+ name : "select_dropdown_option" ,
234+ parameters : {
235+ type : "object" ,
236+ properties : {
237+ selector : { type : "string" } ,
238+ value : { type : "string" }
147239 } ,
148- ] ,
149- } ,
150- ] ,
151- } ,
152- } ) ;
153-
154- // Log the LLM response
155- const responseText = result . text ;
156- console . log ( 'Gemini LLM Raw Response Text:' , responseText ) ;
157- const toolCalls = result . functionCalls ;
158- console . log ( 'Gemini LLM Function Calls:' , toolCalls ) ;
240+ required : [ "selector" , "value" ]
241+ }
242+ }
243+ } ,
244+ {
245+ type : "function" ,
246+ function : {
247+ name : "check_radio_or_checkbox" ,
248+ parameters : {
249+ type : "object" ,
250+ properties : {
251+ selector : { type : "string" } ,
252+ checked : { type : "boolean" }
253+ } ,
254+ required : [ "selector" , "checked" ]
255+ }
256+ }
257+ }
258+ ] ;
259+
260+ const chatCompletion = await aiModel . chat . completions . create ( {
261+ model : modelName ,
262+ messages : [ { role : "user" , content : prompt } ] ,
263+ tools : tools ,
264+ tool_choice : "auto"
265+ } ) ;
266+
267+ toolCalls = chatCompletion . choices [ 0 ] . message . tool_calls ;
268+ console . log ( `${ selectedProvider } LLM Raw Response Text:` , chatCompletion . choices [ 0 ] . message . content ) ;
269+ }
270+
271+ console . log ( `${ selectedProvider } LLM Function Calls:` , toolCalls ) ;
159272
160273 if ( toolCalls && toolCalls . length > 0 ) {
161274 chrome . runtime . sendMessage ( { type : 'UPDATE_POPUP_STATUS' , payload : 'Filling fields...' } ) ;
162275 await chrome . tabs . sendMessage ( tabId , { type : 'EXECUTE_ACTIONS' , payload : toolCalls } ) ;
163276 // No sendResponse here for the original message, it was already sent.
164277 // The content script will receive EXECUTE_ACTIONS directly.
165278 } else {
166- chrome . runtime . sendMessage ( { type : 'UPDATE_POPUP_STATUS' , payload : 'No fields to fill or Gemini returned no actions.' } ) ;
279+ chrome . runtime . sendMessage ( {
280+ type : "UPDATE_POPUP_STATUS" ,
281+ payload : `No fields to fill or ${ selectedProvider } returned no actions.`
282+ } ) ;
167283 // No sendResponse here.
168284 }
169285
170286 } catch ( error : any ) { // Explicitly type error as 'any' to access properties
171- console . error ( 'Gemini API call failed:' , error ) ;
172- let userFriendlyMessage = `Error from Gemini : ${ error instanceof Error ? error . message : String ( error ) } ` ;
287+ console . error ( ` ${ selectedProvider } API call failed:` , error ) ;
288+ let userFriendlyMessage = `Error from ${ selectedProvider } : ${ error instanceof Error ? error . message : String ( error ) } ` ;
173289
174- // Check for 429 Quota Exceeded error
175- if ( error . response && error . response . status === 429 ) {
290+ // Check for 429 Quota Exceeded error (Gemini specific)
291+ if ( selectedProvider === "gemini" && error . response && error . response . status === 429 ) {
176292 try {
177293 const errorBody = JSON . parse ( await error . response . text ( ) ) ;
178294 const quotaMetric = errorBody . error ?. details ?. [ 0 ] ?. violations ?. [ 0 ] ?. quotaMetric ;
@@ -193,6 +309,9 @@ async function handleFormDataExtracted(pageContextItems: any[], tabId: number |
193309 console . warn ( 'Failed to parse Gemini 429 error response:' , parseError ) ;
194310 userFriendlyMessage = `Gemini API Quota Exceeded (429). Could not determine retry time.` ;
195311 }
312+ } else if ( selectedProvider === "openai" && error . response && error . response . status === 429 ) {
313+ // OpenAI specific 429 handling (if different)
314+ userFriendlyMessage = `OpenAI API Rate Limit Exceeded. Please try again later.` ;
196315 }
197316
198317 chrome . runtime . sendMessage ( { type : 'UPDATE_POPUP_STATUS' , payload : userFriendlyMessage } ) ;
0 commit comments