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.
@@ -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
+ }