ai-form-fill 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -0
- package/README.md +355 -0
- package/dist/ai-form-fill.js +715 -0
- package/dist/ai-form-fill.umd.cjs +34 -0
- package/dist/ai-form-input.d.ts +458 -0
- package/package.json +62 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
(function(d,a){typeof exports=="object"&&typeof module<"u"?a(exports):typeof define=="function"&&define.amd?define(["exports"],a):(d=typeof globalThis<"u"?globalThis:d||self,a(d.AIFormFill={}))})(this,(function(d){"use strict";let a={ollama:{apiEndpoint:"http://localhost:11434",model:"gemma3:4b"},openai:{apiEndpoint:"http://localhost:5173/api",model:"gpt-5-nano"},perplexity:{apiEndpoint:"http://localhost:5173/api",model:"sonar"},providerDebug:!0,formFillDebug:!0,timeout:3e4};class y{chatEndpoint;listModelsEndpoint;availabilityEndpoint;selectedModel;apiEndpoint;timeout;supportsStructuredResponses=!1;constructor(e){this.apiEndpoint=e?.apiEndpoint||"",this.selectedModel=e?.model||"",this.timeout=e?.timeout||3e4}getSelectedModel(){return this.selectedModel}async setSelectedModel(e){if(!e)return!1;try{const t=await this.listModels();return t&&t.includes(e)?(this.selectedModel=e,!0):(a.providerDebug&&console.warn(`Model "${e}" not found. Available: ${t.join(", ")}`),!1)}catch(t){return a.providerDebug&&console.warn("Could not validate model:",t),this.selectedModel=e,!0}}getName(){return this.providerName}supportsStructuredOutput(){return this.supportsStructuredResponses}}class P extends y{providerType="local"}class k extends y{providerType="remote"}const L=["null","","n/a","none","no value","empty","undefined","unknown","missing"],I=["true","yes","1","checked","on"];function h(o){o.dispatchEvent(new Event("input",{bubbles:!0})),o.dispatchEvent(new Event("change",{bubbles:!0}))}function N(o){return L.includes(o)}function D(o){if(o.id){const t=document.querySelector(`label[for="${o.id}"]`);if(t)return t.textContent?.trim()||""}const e=o.closest("label");return e&&e.textContent?.trim()||""}function O(o,e){let t=null;const r=o.trim();if(/^\d{4}-\d{2}-\d{2}/.test(r))t=new Date(r);else if(/^\d{1,2}[\/.-]\d{1,2}[\/.-]\d{2,4}$/.test(r)){const s=r.split(/[\/.-]/),c=parseInt(s[0],10),f=parseInt(s[1],10);let m=parseInt(s[2],10);m<100&&(m+=2e3),t=new Date(m,c-1,f)}else{const s=Date.parse(r);isNaN(s)||(t=new Date(s))}if(e==="time"){const s=r.match(/(\d{1,2}):(\d{2})(?::(\d{2}))?(?:\s*(am|pm))?/i);if(s){let c=parseInt(s[1],10);const f=s[2],m=s[4]?.toLowerCase();return m==="pm"&&c<12&&(c+=12),m==="am"&&c===12&&(c=0),`${c.toString().padStart(2,"0")}:${f}`}return null}if(!t||isNaN(t.getTime()))return null;const i=t.getFullYear(),n=(t.getMonth()+1).toString().padStart(2,"0"),l=t.getDate().toString().padStart(2,"0"),p=t.getHours().toString().padStart(2,"0"),u=t.getMinutes().toString().padStart(2,"0");switch(e){case"date":return`${i}-${n}-${l}`;case"datetime-local":return`${i}-${n}-${l}T${p}:${u}`;case"month":return`${i}-${n}`;case"week":const s=new Date(i,0,1),c=Math.floor((t.getTime()-s.getTime())/(1440*60*1e3)),f=Math.ceil((c+s.getDay()+1)/7);return`${i}-W${f.toString().padStart(2,"0")}`;default:return`${i}-${n}-${l}`}}function b(o){const e={element:o,type:"text"};if(o instanceof HTMLInputElement?(e.type=o.type,e.name=o.name,e.placeholder=o.placeholder,e.pattern=o.pattern,o.type==="checkbox"&&(e.placeholder=o.value||"checkbox option"),o.type==="radio"&&(e.placeholder=o.value||"radio option")):o instanceof HTMLTextAreaElement?(e.type="textarea",e.name=o.name,e.placeholder=o.placeholder):o instanceof HTMLSelectElement&&(e.type="select",e.name=o.name),o.id){const r=document.querySelector(`label[for="${o.id}"]`);r&&(e.label=r.textContent?.trim())}if(!e.label){const r=o.closest("label");r&&(e.label=r.textContent?.trim())}const t=o.dataset.affHint;return t&&(e.hint=t),e}function F(o){const e=[],t=new Map;o.querySelectorAll('input:not([type="submit"]):not([type="reset"]):not([type="button"]):not([type="hidden"]):not([type="image"]):not([type="file"]), textarea, select').forEach(i=>{if(i instanceof HTMLInputElement&&i.type==="radio"){const n=i.name;n&&(t.has(n)||t.set(n,[]),t.get(n).push(i))}else i instanceof HTMLElement&&e.push(b(i))});for(const[i,n]of t.entries()){if(n.length===0)continue;const l=n[0],p=b(l);p.options=n.map(u=>{let s="";if(u.id){const c=document.querySelector(`label[for="${u.id}"]`);c&&(s=c.textContent?.trim()||"")}if(!s){const c=u.closest("label");c&&(s=c.textContent?.trim()||"")}return{value:u.value,label:s||u.value}});for(const u of n){const s=u.dataset.affHint;s&&(p.hint+=" "+s)}e.push(p)}return e}function C(o,e){const t=I.includes(e);o.checked=t,h(o)}function R(o,e){const t=o.closest("form");if(!t||!o.name)return;const r=t.querySelectorAll(`input[type="radio"][name="${o.name}"]`);for(const i of r){const n=D(i).toLowerCase(),l=i.value.toLowerCase();if(l===e||n===e||l.includes(e)||n.includes(e)||e.includes(l)||e.includes(n)){i.checked=!0,h(i);break}}}function Y(o,e){const t=O(e,o.type);t?(o.value=t,h(o)):a.formFillDebug&&console.warn(`Could not parse date value "${e}" for ${o.type} input`)}function j(o,e,t){let r=Array.from(o.options).find(i=>i.value.toLowerCase()===e||i.text.toLowerCase()===e);r||(r=Array.from(o.options).find(i=>i.value.toLowerCase().includes(e)||i.text.toLowerCase().includes(e)||e.includes(i.value.toLowerCase())||e.includes(i.text.toLowerCase()))),r?(o.value=r.value,h(o)):a.formFillDebug&&console.warn(`No matching option for select. Value: "${t}", Options:`,Array.from(o.options).map(i=>`${i.value} (${i.text})`))}function v(o,e){const t=e.trim().toLowerCase();if(!N(t))if(o instanceof HTMLInputElement)switch(o.type){case"checkbox":C(o,t);break;case"radio":R(o,t);break;case"date":case"datetime-local":case"time":Y(o,e);break;default:o.value=e,h(o)}else o instanceof HTMLTextAreaElement?(o.value=e,h(o)):o instanceof HTMLSelectElement&&j(o,t,e)}function $(o){return o.name||o.label||o.placeholder||"unknown"}function S(o,e){let t=`Generate appropriate content for the following form field:
|
|
2
|
+
|
|
3
|
+
`;return o.label&&(t+=`Field Label: ${o.label}
|
|
4
|
+
`),o.name&&(t+=`Field Name: ${o.name}
|
|
5
|
+
`),t+=`Field Type: ${o.type}
|
|
6
|
+
`,o.placeholder&&(t+=`Placeholder: ${o.placeholder}
|
|
7
|
+
`),o.pattern&&(t+=`Pattern/Format: ${o.pattern}
|
|
8
|
+
`),e&&(t+=`
|
|
9
|
+
Additional Context: ${e}
|
|
10
|
+
`),o.type==="checkbox"?t=`${e}
|
|
11
|
+
Randomly return "true" or "false", no explanations. Dont repeat your choice too often.`:t+=`
|
|
12
|
+
Provide a realistic and appropriate value for this field. Only return the value itself, no explanations.`,t}function M(o,e){let t=`Extract structured data from the following unstructured text and match it to the form fields.
|
|
13
|
+
|
|
14
|
+
`;t+=`Form fields:
|
|
15
|
+
`;for(const r of o){const i=r.name||r.label||r.placeholder||"unknown";if(t+=`- ${i} (type: ${r.type})`,r.label&&(t+=` - Label: "${r.label}"`),r.placeholder&&(t+=` - Placeholder: "${r.placeholder}"`),r.type==="select"&&r.element instanceof HTMLSelectElement){const n=Array.from(r.element.options).map(l=>l.textContent?.trim()||"").filter(l=>l);t+=` - Options: [${n.join(", ")}]`}if(r.type==="radio"&&r.options){const n=r.options.map(l=>l.label||l.value);t+=` - Options: [${n.join(", ")}]`}r.type==="date"?t+=" - Format: YYYY-MM-DD":r.type==="datetime-local"?t+=" - Format: YYYY-MM-DDTHH:MM":r.type==="time"&&(t+=" - Format: HH:MM"),r.hint&&(t+=` - Additional info: ${r.hint}`),t+=`
|
|
16
|
+
`}return t+=`
|
|
17
|
+
Unstructured text:
|
|
18
|
+
${e}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
Extract the relevant information and return it as a JSON object where keys match the field names exactly.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
Only include fields where you found relevant data.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
For checkbox fields, return "true" if the text indicates the option should be checked, "false" or omit otherwise.
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
For radio fields, return the value (preferred) or label of the selected option.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
Return ONLY the JSON object, no explanations or markdown formatting.
|
|
34
|
+
`,t}const E={FIELD_FILL:"You are a helpful assistant that generates appropriate content for form fields. Provide only the value to fill in the field, without any explanation or additional text.",PARSE_EXTRACT:'You are a helpful assistant that extracts structured data from unstructured text. You must respond ONLY with valid JSON, no explanations or markdown code blocks. If its a checkbox field, return "true" if it should be checked, otherwise return "false" or omit the field.'};function H(o){const e={};for(const t of o){const r=t.name||t.label;if(!r)continue;let i;switch(t.type){case"number":case"range":i={type:"number"};break;case"boolean":case"checkbox":i={type:"boolean"};break;case"url":i={type:"string",format:"uri"};break;case"date":i={type:"string",format:"date"};break;case"datetime-local":i={type:"string",format:"date-time"};break;case"time":i={type:"string",format:"time"};break;default:i={type:"string"};break}if(t.pattern&&(i.pattern=t.pattern),t.placeholder||t.hint){const n=[];t.placeholder&&n.push(t.placeholder),t.hint&&n.push(t.hint),i.description=n.join(" - ")}e[r]=i}return{type:"object",properties:e,additionalProperties:!1}}function T(o){try{let e=o.trim();e=e.replace(/```json\n?/g,"").replace(/```\n?/g,"").trim();const t=JSON.parse(e),r={};for(const[i,n]of Object.entries(t))r[i]=String(n);return r}catch(e){return console.error("Failed to parse JSON response:",e),console.error("Response was:",o),{}}}function J(o){try{return JSON.parse(o),!0}catch{return!1}}class A extends P{providerName="ollama";supportsStructuredResponses=!0;chatEndpoint;listModelsEndpoint;availabilityEndpoint;constructor(e){super({apiEndpoint:e?.apiEndpoint||a.ollama.apiEndpoint,model:e?.model||a.ollama.model,timeout:e?.timeout||a.timeout}),this.chatEndpoint=this.apiEndpoint+"/api/chat",this.listModelsEndpoint=this.apiEndpoint+"/api/tags",this.availabilityEndpoint=this.apiEndpoint+"/api/tags"}async chat(e){const t=new AbortController,r=setTimeout(()=>t.abort(),this.timeout),i=this.chatEndpoint;try{const n={model:e.model,messages:e.messages,stream:!1,options:{num_predict:e.maxTokens}},l=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n),signal:t.signal});if(!l.ok)throw new Error(`Ollama API error: ${l.status} ${l.statusText}`);const p=await l.json();return{content:p.message.content,model:p.model,finishReason:p.done?"stop":"length"}}catch(n){if(n instanceof Error){if(n.name==="AbortError")throw new Error(`Ollama request timed out after ${this.timeout}ms`);if(n.message.includes("fetch")||n.message.includes("Failed to fetch"))throw new Error(`Failed to connect to Ollama at ${this.apiEndpoint}. Is Ollama running?`)}throw n}finally{clearTimeout(r)}}async listModels(){try{const e=await fetch(this.listModelsEndpoint);if(!e.ok)throw new Error(`Failed to fetch models: ${e.statusText}`);return((await e.json()).models||[]).map(r=>r.name)}catch(e){return console.error("Error listing Ollama models:",e),[]}}async isAvailable(){try{return(await fetch(this.availabilityEndpoint,{method:"GET"})).ok}catch{return!1}}}class w extends k{providerName="openai";supportsStructuredResponses=!0;chatEndpoint;listModelsEndpoint;availabilityEndpoint;constructor(e){super({apiEndpoint:e?.apiEndpoint||a.openai.apiEndpoint,model:e?.model||a.openai.model,timeout:e?.timeout||a.timeout}),this.chatEndpoint=`${this.apiEndpoint}/${this.providerName}/chat`,this.listModelsEndpoint=`${this.apiEndpoint}/${this.providerName}/models`,this.availabilityEndpoint=`${this.apiEndpoint}/${this.providerName}/available`}async chat(e){const t=new AbortController,r=setTimeout(()=>t.abort(),this.timeout),i=this.chatEndpoint;try{const l=await(await fetch(i,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:t.signal})).json();return a.providerDebug&&console.log(`${this.providerName} response body:`,l),{content:l.choices[0].message.content,model:l.model,finishReason:l.choices[0].finish_reason}}catch(n){if(n instanceof Error){if(n.name==="AbortError")throw new Error(`${this.providerName} request timed out after ${this.timeout}ms`);if(n.message.includes("fetch")||n.message.includes("Failed to fetch"))throw new Error(`Failed to connect to ${this.providerName}. Check your network connection.`)}throw n}finally{clearTimeout(r)}}async listModels(){const e=this.listModelsEndpoint;try{const t=await fetch(e,{method:"POST"});if(!t.ok)throw new Error(`${this.providerName} API error: ${t.status} ${t.statusText}`);return(await t.json()).models}catch(t){if(a.providerDebug)throw new Error(`Error fetching models from ${this.providerName}: ${t}`);return[]}}async isAvailable(){const e=this.availabilityEndpoint;try{return(await fetch(e,{method:"POST"})).ok}catch(t){if(a.providerDebug)throw t;return!1}}}class x extends w{providerName="perplexity";constructor(e){super({apiEndpoint:e?.apiEndpoint||a.perplexity.apiEndpoint,model:e?.model||a.perplexity.model,timeout:e?.timeout||a.timeout}),this.chatEndpoint=`${this.apiEndpoint}/${this.providerName}/chat`,this.listModelsEndpoint=`${this.apiEndpoint}/${this.providerName}/models`,this.availabilityEndpoint=`${this.apiEndpoint}/${this.providerName}/available`}}class g{provider;allowedProviders;selectedFields;constructor(e,t){e instanceof y?this.provider=e:this.provider=g.constructProviderWithName(e,t),this.selectedFields=t?.targetFields,this.allowedProviders=t?.allowedProviders}async fillSingleField(e){const t=b(e);a.formFillDebug&&console.log(`Filling ${t.type} field: ${t.name}`);const r=S(t),i=[{role:"system",content:E.FIELD_FILL},{role:"user",content:r}];try{const n=await this.provider.chat({messages:i,model:this.provider.getSelectedModel()});n.content&&v(e,n.content.trim()),a.formFillDebug&&console.log("Field filled with:",n.content)}catch(n){a.formFillDebug&&console.error("Error during fillSingleField:",n)}}async parseAndFillForm(e,t){const r=F(e);a.formFillDebug&&(console.log("Parsing unstructured text for",r.length,"fields"),console.log("Unstructured text:",r));const i=this.selectedFields?r.filter(s=>s.name&&this.selectedFields.includes(s.name)):r,n=M(i,t);a.formFillDebug&&(console.groupCollapsed("Constructed parse prompt:"),console.log(n),console.groupEnd(),console.log(`Sending prompt to ${this.provider.getName()}'s ${this.provider.getSelectedModel()} model...`));const p={messages:[{role:"system",content:E.PARSE_EXTRACT},{role:"user",content:n}],model:this.provider.getSelectedModel()};this.provider.supportsStructuredOutput()&&(p.format=H(i),a.formFillDebug&&console.log("Using structured output format:",p.format));let u={};try{const s=await this.provider.chat(p);if(!s.content){a.formFillDebug&&console.warn("No content received from AI provider.");return}u=T(s.content)}catch(s){a.formFillDebug&&console.error("Error calling AI provider:",s);return}a.formFillDebug&&console.log("Extracted data:",u);for(const s of i){const c=$(s);if(c&&u[c])try{v(s.element,u[c])}catch(f){a.formFillDebug&&console.error(`Failed to fill field "${c}":`,f)}}}async getAvailableModels(){return this.provider.listModels?await this.provider.listModels():[]}async setSelectedModel(e){return this.provider.setSelectedModel(e)}getSelectedModel(){return this.provider.getSelectedModel()}setFields(e){this.selectedFields=e||void 0}getFields(){return this.selectedFields}async providerAvailable(){return this.provider.isAvailable?await this.provider.isAvailable():!0}setProvider(e){this.provider=e}getProvider(){return this.provider}getListOfAllowedProviders(){return this.allowedProviders}static constructProviderWithName(e,t){const r={apiEndpoint:t?.apiEndpoint||"",model:t?.model||"",timeout:t?.timeout};return{ollama:()=>new A(r),openai:()=>new w(r),perplexity:()=>new x(r)}[e]()}}function q(o="aff-form"){const e=document.getElementById(o),t=document.getElementById("aff-text"),r=document.getElementById("aff-text-button"),i=e.getAttribute("data-aff-provider")||"ollama",n=new g(i,{debug:!0});r?r.addEventListener("click",async()=>{const l=t.value.trim();try{await n.parseAndFillForm(e,l)}catch(p){console.error("Error filling form:",p)}}):console.warn("AI Form Fill button not found")}d.AIFormFill=g,d.AIProvider=y,d.LocalOllamaProvider=A,d.OpenAIProvider=w,d.PerplexityProvider=x,d.SYSTEM_PROMPTS=E,d.affConfig=a,d.analyzeField=b,d.buildFieldPrompt=S,d.buildParsePrompt=M,d.getFieldIdentifier=$,d.getFillTargets=F,d.initializeAFFQuick=q,d.isValidJson=J,d.parseJsonResponse=T,d.setFieldValue=v,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
|
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default configuration for AI Form Input library
|
|
3
|
+
*
|
|
4
|
+
* Users can modify these defaults by importing and changing values:
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { config } from 'ai-form-input';
|
|
9
|
+
*
|
|
10
|
+
* // Change Ollama default endpoint
|
|
11
|
+
* config.ollama.apiEndpoint = 'http://my-server:11434';
|
|
12
|
+
*
|
|
13
|
+
* // Change OpenAI to use real API
|
|
14
|
+
* config.openai.apiEndpoint = 'https://api.openai.com/v1';
|
|
15
|
+
* config.openai.model = 'gpt-4';
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare let affConfig: {
|
|
19
|
+
ollama: {
|
|
20
|
+
apiEndpoint: string;
|
|
21
|
+
model: string;
|
|
22
|
+
};
|
|
23
|
+
openai: {
|
|
24
|
+
apiEndpoint: string;
|
|
25
|
+
model: string;
|
|
26
|
+
};
|
|
27
|
+
perplexity: {
|
|
28
|
+
apiEndpoint: string;
|
|
29
|
+
model: string;
|
|
30
|
+
};
|
|
31
|
+
providerDebug: boolean;
|
|
32
|
+
formFillDebug: boolean;
|
|
33
|
+
timeout: number;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Main class for AI-powered form input
|
|
38
|
+
*
|
|
39
|
+
* Provides high-level methods for filling forms using AI. Supports:
|
|
40
|
+
* - Extracting structured data from unstructured text
|
|
41
|
+
* - Filling entire forms automatically
|
|
42
|
+
* - Generating content for individual fields
|
|
43
|
+
* - Multiple AI providers (Ollama, OpenAI, custom)
|
|
44
|
+
*
|
|
45
|
+
*/
|
|
46
|
+
export declare class AIFormFill {
|
|
47
|
+
private provider;
|
|
48
|
+
private allowedProviders?;
|
|
49
|
+
private selectedFields?;
|
|
50
|
+
constructor(desiredProvider: AvailableProviders | AIProvider, options?: AIFormFillConfig & Partial<ProviderConfig>);
|
|
51
|
+
/**
|
|
52
|
+
* Fill a single form field with AI-generated content
|
|
53
|
+
*
|
|
54
|
+
* Generates appropriate content for one field based on its label, name,
|
|
55
|
+
* placeholder, and type. Useful for creative content or when you don't
|
|
56
|
+
* have source text to extract from.
|
|
57
|
+
*
|
|
58
|
+
* @param element - The form field element to fill (input, textarea, or select)
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* const bioField = document.querySelector('#bio');
|
|
63
|
+
* await aiForm.fillSingleField(bioField);
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
fillSingleField(element: HTMLElement): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Parse unstructured text and automatically fill matching form fields
|
|
69
|
+
*
|
|
70
|
+
* @param formElement - The HTML form to fill
|
|
71
|
+
* @param unstructuredText - The source text to extract data from
|
|
72
|
+
* - Examples: Resume text, email body, paragraph descriptions, JSON strings
|
|
73
|
+
*/
|
|
74
|
+
parseAndFillForm(formElement: HTMLFormElement, unstructuredText: string): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Get list of available models from the form's provider
|
|
77
|
+
*/
|
|
78
|
+
getAvailableModels(): Promise<string[]>;
|
|
79
|
+
/**
|
|
80
|
+
* Set the model to use for chat requests
|
|
81
|
+
*/
|
|
82
|
+
setSelectedModel(modelName: string): Promise<boolean>;
|
|
83
|
+
/**
|
|
84
|
+
* Get the currently selected model
|
|
85
|
+
*/
|
|
86
|
+
getSelectedModel(): string;
|
|
87
|
+
/**
|
|
88
|
+
* Set which fields should be filled
|
|
89
|
+
*/
|
|
90
|
+
setFields(fields: string[] | undefined): void;
|
|
91
|
+
/**
|
|
92
|
+
* Get the currently configured field targets
|
|
93
|
+
*
|
|
94
|
+
* @returns Array of field names being targeted, or undefined if all fields are targeted
|
|
95
|
+
*/
|
|
96
|
+
getFields(): string[] | undefined;
|
|
97
|
+
/**
|
|
98
|
+
* Check if the AI provider is available and responding
|
|
99
|
+
*
|
|
100
|
+
* @returns Promise resolving to true if provider is available, false otherwise
|
|
101
|
+
*/
|
|
102
|
+
providerAvailable(): Promise<boolean>;
|
|
103
|
+
/**
|
|
104
|
+
* Change the AI provider
|
|
105
|
+
*/
|
|
106
|
+
setProvider(provider: AIProvider): void;
|
|
107
|
+
/**
|
|
108
|
+
* Get the current AI provider
|
|
109
|
+
*/
|
|
110
|
+
getProvider(): AIProvider;
|
|
111
|
+
/**
|
|
112
|
+
* Get the list of allowed providers, if any
|
|
113
|
+
*/
|
|
114
|
+
getListOfAllowedProviders(): AIProvider[] | undefined;
|
|
115
|
+
/**
|
|
116
|
+
* Setup the AI provider based on the desired provider name
|
|
117
|
+
*/
|
|
118
|
+
private static constructProviderWithName;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Configuration for the AIFormFill class
|
|
123
|
+
*
|
|
124
|
+
* @example Basic usage
|
|
125
|
+
* ```typescript
|
|
126
|
+
* const config: AIFormFillConfig = {
|
|
127
|
+
* debug: true
|
|
128
|
+
* };
|
|
129
|
+
* ```
|
|
130
|
+
*
|
|
131
|
+
* @example With field filtering
|
|
132
|
+
* ```typescript
|
|
133
|
+
* const config: AIFormFillConfig = {
|
|
134
|
+
* fields: ['firstName', 'lastName', 'email'],
|
|
135
|
+
* debug: true
|
|
136
|
+
* };
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
export declare type AIFormFillConfig = {
|
|
140
|
+
/**
|
|
141
|
+
* Optional array of field names to target.
|
|
142
|
+
* If provided, only these fields will be filled (whitelist).
|
|
143
|
+
* If omitted, all detected fields are filled.
|
|
144
|
+
*/
|
|
145
|
+
targetFields?: string[];
|
|
146
|
+
/**
|
|
147
|
+
* Optional array of allowed AI providers.
|
|
148
|
+
* If provided, only these providers can be used.
|
|
149
|
+
*/
|
|
150
|
+
allowedProviders?: AIProvider[];
|
|
151
|
+
/** Enable console logging for debugging (default: false) */
|
|
152
|
+
debug?: boolean;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Base class that all AI providers must extend
|
|
157
|
+
*
|
|
158
|
+
* Providers are responsible for:
|
|
159
|
+
* - Making API calls to their respective AI services (using fetch, axios, SDKs, etc.)
|
|
160
|
+
* - Translating provider-specific request/response formats to the standard ChatParams/ChatResponse
|
|
161
|
+
* - Handling authentication, rate limiting, and error handling
|
|
162
|
+
* - Implementing optional features like model listing and availability checks
|
|
163
|
+
*
|
|
164
|
+
* @see Documentation: {@link AIProvider}
|
|
165
|
+
*/
|
|
166
|
+
export declare abstract class AIProvider {
|
|
167
|
+
protected abstract providerName: string;
|
|
168
|
+
protected abstract providerType: ProviderType;
|
|
169
|
+
/**
|
|
170
|
+
* **Optional**: Concrete link to endpoint that sends chat messages
|
|
171
|
+
*/
|
|
172
|
+
protected chatEndpoint?: string;
|
|
173
|
+
/**
|
|
174
|
+
* **Optional**: Concrete link to endpoint that lists available models
|
|
175
|
+
*/
|
|
176
|
+
protected listModelsEndpoint?: string;
|
|
177
|
+
/**
|
|
178
|
+
* **Optional**: Concrete link to endpoint that checks API availability
|
|
179
|
+
*/
|
|
180
|
+
protected availabilityEndpoint?: string;
|
|
181
|
+
protected selectedModel: string;
|
|
182
|
+
protected apiEndpoint: string;
|
|
183
|
+
protected timeout: number;
|
|
184
|
+
protected supportsStructuredResponses: boolean;
|
|
185
|
+
constructor(config?: ProviderConfig);
|
|
186
|
+
/**
|
|
187
|
+
* Sends a message to a model of the AI provider and returns the response
|
|
188
|
+
* @param params - The {@link ChatRequest | chat request} including messages, model, etc.
|
|
189
|
+
* @returns A promise that resolves to a {@link ChatResponse}
|
|
190
|
+
*/
|
|
191
|
+
abstract chat(params: ChatRequest): Promise<ChatResponse>;
|
|
192
|
+
/** Returns the currently selected model. */
|
|
193
|
+
getSelectedModel(): string;
|
|
194
|
+
/**
|
|
195
|
+
* Sets the model to use for chat requests. Validates against available models if possible.
|
|
196
|
+
*/
|
|
197
|
+
setSelectedModel(modelName: string): Promise<boolean>;
|
|
198
|
+
/**
|
|
199
|
+
* Lists available provider models
|
|
200
|
+
*
|
|
201
|
+
* @returns The currently configured model(s) as a Promise resolving to an array of model names
|
|
202
|
+
*/
|
|
203
|
+
abstract listModels(): Promise<string[]>;
|
|
204
|
+
/**
|
|
205
|
+
* **Optional**: Checks if the provider's API is accessible
|
|
206
|
+
*
|
|
207
|
+
* @returns Promise resolving to true if the API is accessible
|
|
208
|
+
*/
|
|
209
|
+
abstract isAvailable(): Promise<boolean>;
|
|
210
|
+
getName(): string;
|
|
211
|
+
/**
|
|
212
|
+
* Indicates if the provider supports structured output formats (e.g., JSON Schema)
|
|
213
|
+
*
|
|
214
|
+
* @returns true if structured output is supported, false otherwise
|
|
215
|
+
*/
|
|
216
|
+
supportsStructuredOutput(): boolean;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Extracts metadata from a form field element (type, name, label, placeholder, etc.).
|
|
221
|
+
*/
|
|
222
|
+
export declare function analyzeField(element: HTMLElement): FieldInfo;
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* All currently implemented provider names
|
|
226
|
+
*/
|
|
227
|
+
export declare type AvailableProviders = 'openai' | 'ollama' | 'perplexity';
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Build a prompt for filling a single form field
|
|
231
|
+
*
|
|
232
|
+
* Constructs a detailed prompt that describes the field's purpose, type,
|
|
233
|
+
* validation rules, and any additional context. Used by fillSingleField().
|
|
234
|
+
*
|
|
235
|
+
* @param field - The FieldInfo object describing the field
|
|
236
|
+
* @param context - Optional additional context or instructions for the AI
|
|
237
|
+
* @returns A formatted prompt string ready for the AI
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```typescript
|
|
241
|
+
* const field = {
|
|
242
|
+
* label: 'Professional Bio',
|
|
243
|
+
* type: 'textarea',
|
|
244
|
+
* placeholder: 'Tell us about yourself...',
|
|
245
|
+
* required: true
|
|
246
|
+
* };
|
|
247
|
+
*
|
|
248
|
+
* const prompt = buildFieldPrompt(field, 'Make it friendly and professional');
|
|
249
|
+
* // Returns detailed prompt including label, type, requirements, and context
|
|
250
|
+
* ```
|
|
251
|
+
*/
|
|
252
|
+
export declare function buildFieldPrompt(field: FieldInfo, context?: string): string;
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Builds a prompt for AI to extract data from unstructured text into form fields.
|
|
256
|
+
*/
|
|
257
|
+
export declare function buildParsePrompt(clientFieldInfos: FieldInfo[], unstructuredText: string): string;
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* A single message in a chat conversation
|
|
261
|
+
*
|
|
262
|
+
* @param role - The role of the message sender (system, user, or assistant)
|
|
263
|
+
* @param content - The content of the message
|
|
264
|
+
*/
|
|
265
|
+
export declare type ChatMessage = {
|
|
266
|
+
role: 'system' | 'user' | 'assistant';
|
|
267
|
+
content: string;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Parameters for a chat completion request
|
|
272
|
+
*/
|
|
273
|
+
export declare type ChatRequest = {
|
|
274
|
+
messages: ChatMessage[];
|
|
275
|
+
model: string;
|
|
276
|
+
maxTokens?: number;
|
|
277
|
+
format?: Record<string, any>;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Response from a chat completion request
|
|
282
|
+
*/
|
|
283
|
+
export declare type ChatResponse = {
|
|
284
|
+
content: string | null;
|
|
285
|
+
model?: string;
|
|
286
|
+
finishReason?: string;
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Information about a form field
|
|
291
|
+
*/
|
|
292
|
+
export declare type FieldInfo = {
|
|
293
|
+
element: HTMLElement;
|
|
294
|
+
type: string;
|
|
295
|
+
name?: string;
|
|
296
|
+
label?: string;
|
|
297
|
+
placeholder?: string;
|
|
298
|
+
pattern?: string;
|
|
299
|
+
hint?: string;
|
|
300
|
+
/** For radio buttons: array of available options with value and label */
|
|
301
|
+
options?: Array<{
|
|
302
|
+
value: string;
|
|
303
|
+
label: string;
|
|
304
|
+
}>;
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Returns the best identifier for a field (name > label > placeholder > 'unknown').
|
|
309
|
+
*/
|
|
310
|
+
export declare function getFieldIdentifier(field: FieldInfo): string;
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Returns all fillable fields from a form (inputs, textareas, selects).
|
|
314
|
+
* Radio buttons are grouped by name into a single FieldInfo with options.
|
|
315
|
+
*/
|
|
316
|
+
export declare function getFillTargets(formElement: HTMLFormElement): FieldInfo[];
|
|
317
|
+
|
|
318
|
+
export declare function initializeAFFQuick(formId?: string): void;
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Returns true if the string is valid JSON.
|
|
322
|
+
*/
|
|
323
|
+
export declare function isValidJson(str: string): boolean;
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* @extension Extend this class for providers that run locally (e.g., Ollama, LocalAI)
|
|
327
|
+
*/
|
|
328
|
+
declare abstract class LocalAIProvider extends AIProvider {
|
|
329
|
+
readonly providerType: ProviderType;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Provider implementation for locally running Ollama instance
|
|
334
|
+
*
|
|
335
|
+
* Ollama is a popular local AI runtime that supports many open-source models.
|
|
336
|
+
* This implementation uses the Ollama REST API with no external dependencies.
|
|
337
|
+
*
|
|
338
|
+
* @example
|
|
339
|
+
* ```typescript
|
|
340
|
+
* const provider = new LocalOllamaProvider({
|
|
341
|
+
* apiEndpoint: 'http://localhost:11434',
|
|
342
|
+
* model: 'gemma3:4b',
|
|
343
|
+
* timeout: 30000,
|
|
344
|
+
* });
|
|
345
|
+
* ```
|
|
346
|
+
* @see {@link https://docs.ollama.com/api/introduction | Ollama API Documentation}
|
|
347
|
+
*/
|
|
348
|
+
export declare class LocalOllamaProvider extends LocalAIProvider {
|
|
349
|
+
protected providerName: string;
|
|
350
|
+
protected supportsStructuredResponses: boolean;
|
|
351
|
+
protected chatEndpoint: string;
|
|
352
|
+
protected listModelsEndpoint: string;
|
|
353
|
+
protected availabilityEndpoint: string;
|
|
354
|
+
constructor(config?: ProviderConfig);
|
|
355
|
+
chat(params: ChatRequest): Promise<ChatResponse>;
|
|
356
|
+
listModels(): Promise<string[]>;
|
|
357
|
+
isAvailable(): Promise<boolean>;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Provider implementation for OpenAI's API
|
|
362
|
+
*
|
|
363
|
+
* @example
|
|
364
|
+
* ```typescript
|
|
365
|
+
* const provider = new OpenAIProvider({
|
|
366
|
+
* model: 'gpt-5-nano',
|
|
367
|
+
* timeout: 60000,
|
|
368
|
+
* });
|
|
369
|
+
* ```
|
|
370
|
+
* @see {@link https://platform.openai.com/docs/guides/text?prompt-templates-examples=filevar | OpenAI API Documentation}
|
|
371
|
+
*/
|
|
372
|
+
export declare class OpenAIProvider extends RemoteAIProvider {
|
|
373
|
+
protected providerName: string;
|
|
374
|
+
protected supportsStructuredResponses: boolean;
|
|
375
|
+
protected chatEndpoint: string;
|
|
376
|
+
protected listModelsEndpoint: string;
|
|
377
|
+
protected availabilityEndpoint: string;
|
|
378
|
+
constructor(config?: ProviderConfig);
|
|
379
|
+
chat(params: ChatRequest): Promise<ChatResponse>;
|
|
380
|
+
listModels(): Promise<string[]>;
|
|
381
|
+
isAvailable(): Promise<boolean>;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Utility functions for parsing JSON responses from AI providers
|
|
386
|
+
*/
|
|
387
|
+
/**
|
|
388
|
+
* Parses JSON from AI responses, handling markdown code blocks and formatting issues.
|
|
389
|
+
* Returns empty object if parsing fails.
|
|
390
|
+
*/
|
|
391
|
+
export declare function parseJsonResponse(aiResponse: string): Record<string, string>;
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Provider implementation for Perplexity AI's API
|
|
395
|
+
*
|
|
396
|
+
* @see {@link https://docs.perplexity.ai/getting-started/overview | Perplexity API Documentation}
|
|
397
|
+
*/
|
|
398
|
+
export declare class PerplexityProvider extends OpenAIProvider {
|
|
399
|
+
protected providerName: string;
|
|
400
|
+
constructor(config?: ProviderConfig);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Configuration options for AI providers.
|
|
405
|
+
*/
|
|
406
|
+
export declare interface ProviderConfig {
|
|
407
|
+
apiEndpoint?: string;
|
|
408
|
+
model?: string;
|
|
409
|
+
timeout?: number;
|
|
410
|
+
chatEndpoint?: string;
|
|
411
|
+
listModelsEndpoint?: string;
|
|
412
|
+
availabilityEndpoint?: string;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export declare type ProviderType = 'local' | 'remote';
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* @extension Extend this class for providers that run remotely (e.g., OpenAI, Perplexity)
|
|
419
|
+
*/
|
|
420
|
+
declare abstract class RemoteAIProvider extends AIProvider {
|
|
421
|
+
readonly providerType: ProviderType;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Sets the value of a form field and triggers change events for framework reactivity.
|
|
426
|
+
*/
|
|
427
|
+
export declare function setFieldValue(element: HTMLElement, value: string): void;
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* System prompts for different AI tasks
|
|
431
|
+
*
|
|
432
|
+
* Predefined system messages that set the AI's behavior for specific tasks.
|
|
433
|
+
* These are sent as the first message in every conversation to establish
|
|
434
|
+
* the AI's role and response format.
|
|
435
|
+
*
|
|
436
|
+
* @property FIELD_FILL - For single field generation tasks
|
|
437
|
+
* - Instructs AI to return only the value, no explanations
|
|
438
|
+
* - Used by fillSingleField()
|
|
439
|
+
*
|
|
440
|
+
* @property PARSE_EXTRACT - For data extraction from unstructured text
|
|
441
|
+
* - Instructs AI to return only valid JSON
|
|
442
|
+
* - Prevents markdown code blocks and explanations
|
|
443
|
+
* - Used by parseAndFillForm()
|
|
444
|
+
*
|
|
445
|
+
* @example
|
|
446
|
+
* ```typescript
|
|
447
|
+
* const messages = [
|
|
448
|
+
* { role: 'system', content: SYSTEM_PROMPTS.PARSE_EXTRACT },
|
|
449
|
+
* { role: 'user', content: userPrompt }
|
|
450
|
+
* ];
|
|
451
|
+
* ```
|
|
452
|
+
*/
|
|
453
|
+
export declare const SYSTEM_PROMPTS: {
|
|
454
|
+
readonly FIELD_FILL: "You are a helpful assistant that generates appropriate content for form fields. Provide only the value to fill in the field, without any explanation or additional text.";
|
|
455
|
+
readonly PARSE_EXTRACT: "You are a helpful assistant that extracts structured data from unstructured text. You must respond ONLY with valid JSON, no explanations or markdown code blocks. If its a checkbox field, return \"true\" if it should be checked, otherwise return \"false\" or omit the field.";
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
export { }
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ai-form-fill",
|
|
3
|
+
"description": "Framework-agnostic library for AI-powered form filling. Extract structured data from unstructured text and automatically fill forms using OpenAI, Ollama, or custom AI providers.",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ai",
|
|
7
|
+
"form",
|
|
8
|
+
"autofill",
|
|
9
|
+
"llm",
|
|
10
|
+
"openai",
|
|
11
|
+
"ollama",
|
|
12
|
+
"perplexity",
|
|
13
|
+
"typescript",
|
|
14
|
+
"form-filling",
|
|
15
|
+
"data-extraction",
|
|
16
|
+
"nlp",
|
|
17
|
+
"machine-learning",
|
|
18
|
+
"automation"
|
|
19
|
+
],
|
|
20
|
+
"author": "Joel Deffner",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": ""
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/JDeffner",
|
|
27
|
+
"type": "module",
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
33
|
+
"types": "./dist/ai-form-input.d.ts",
|
|
34
|
+
"main": "./dist/ai-form-input.umd.cjs",
|
|
35
|
+
"module": "./dist/ai-form-input.js",
|
|
36
|
+
"exports": {
|
|
37
|
+
".": {
|
|
38
|
+
"types": "./dist/ai-form-input.d.ts",
|
|
39
|
+
"import": "./dist/ai-form-input.js",
|
|
40
|
+
"require": "./dist/ai-form-input.umd.cjs"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"dev": "vite",
|
|
45
|
+
"build": "tsc && vite build",
|
|
46
|
+
"preview": "vite preview",
|
|
47
|
+
"test": "vitest"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/jsdom": "^21.1.7",
|
|
51
|
+
"@types/node": "^25.0.10",
|
|
52
|
+
"jsdom": "^27.4.0",
|
|
53
|
+
"openai": "^6.16.0",
|
|
54
|
+
"typescript": "~5.9.3",
|
|
55
|
+
"vite": "^7.3.1",
|
|
56
|
+
"vite-plugin-dts": "^4.5.4",
|
|
57
|
+
"vite-plugin-mock-dev-server": "^2.0.7",
|
|
58
|
+
"vitest": "^4.0.18"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
}
|
|
62
|
+
}
|