├── guides ├── open-ai-key │ ├── images │ │ ├── create.png │ │ ├── copy-key.png │ │ ├── dashboard.png │ │ ├── copy-new-key.png │ │ ├── name-new-key.png │ │ ├── payment-form.png │ │ ├── buying-credits.png │ │ ├── invite-my-team.png │ │ ├── key-and-project.png │ │ ├── new-secret-key.png │ │ ├── start-building.png │ │ ├── create-new-secret-key.png │ │ ├── create-an-organization.png │ │ └── create-open-ai-account.png │ └── README.md └── vanilla-with-express │ ├── images │ ├── working.png │ ├── click-paste.png │ ├── with-paste.png │ ├── error-message.png │ ├── unstyled-form.png │ └── completed-project.png │ └── README.md ├── .gitignore ├── src ├── shared │ └── types.ts ├── component │ ├── utils.ts │ └── index.ts └── extractor │ └── index.ts ├── vite.config.component.ts ├── vite.config.extractor.ts ├── tsconfig.json ├── package.json └── README.md /guides/open-ai-key/images/create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitovi/ai-component-paste/HEAD/guides/open-ai-key/images/create.png -------------------------------------------------------------------------------- /guides/open-ai-key/images/copy-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitovi/ai-component-paste/HEAD/guides/open-ai-key/images/copy-key.png -------------------------------------------------------------------------------- /guides/open-ai-key/images/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitovi/ai-component-paste/HEAD/guides/open-ai-key/images/dashboard.png -------------------------------------------------------------------------------- /guides/open-ai-key/images/copy-new-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitovi/ai-component-paste/HEAD/guides/open-ai-key/images/copy-new-key.png -------------------------------------------------------------------------------- /guides/open-ai-key/images/name-new-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitovi/ai-component-paste/HEAD/guides/open-ai-key/images/name-new-key.png -------------------------------------------------------------------------------- /guides/open-ai-key/images/payment-form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitovi/ai-component-paste/HEAD/guides/open-ai-key/images/payment-form.png -------------------------------------------------------------------------------- /guides/open-ai-key/images/buying-credits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitovi/ai-component-paste/HEAD/guides/open-ai-key/images/buying-credits.png -------------------------------------------------------------------------------- /guides/open-ai-key/images/invite-my-team.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitovi/ai-component-paste/HEAD/guides/open-ai-key/images/invite-my-team.png -------------------------------------------------------------------------------- /guides/open-ai-key/images/key-and-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitovi/ai-component-paste/HEAD/guides/open-ai-key/images/key-and-project.png -------------------------------------------------------------------------------- /guides/open-ai-key/images/new-secret-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitovi/ai-component-paste/HEAD/guides/open-ai-key/images/new-secret-key.png -------------------------------------------------------------------------------- /guides/open-ai-key/images/start-building.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitovi/ai-component-paste/HEAD/guides/open-ai-key/images/start-building.png -------------------------------------------------------------------------------- /guides/vanilla-with-express/images/working.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitovi/ai-component-paste/HEAD/guides/vanilla-with-express/images/working.png -------------------------------------------------------------------------------- /guides/open-ai-key/images/create-new-secret-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitovi/ai-component-paste/HEAD/guides/open-ai-key/images/create-new-secret-key.png -------------------------------------------------------------------------------- /guides/vanilla-with-express/images/click-paste.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitovi/ai-component-paste/HEAD/guides/vanilla-with-express/images/click-paste.png -------------------------------------------------------------------------------- /guides/vanilla-with-express/images/with-paste.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitovi/ai-component-paste/HEAD/guides/vanilla-with-express/images/with-paste.png -------------------------------------------------------------------------------- /guides/open-ai-key/images/create-an-organization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitovi/ai-component-paste/HEAD/guides/open-ai-key/images/create-an-organization.png -------------------------------------------------------------------------------- /guides/open-ai-key/images/create-open-ai-account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitovi/ai-component-paste/HEAD/guides/open-ai-key/images/create-open-ai-account.png -------------------------------------------------------------------------------- /guides/vanilla-with-express/images/error-message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitovi/ai-component-paste/HEAD/guides/vanilla-with-express/images/error-message.png -------------------------------------------------------------------------------- /guides/vanilla-with-express/images/unstyled-form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitovi/ai-component-paste/HEAD/guides/vanilla-with-express/images/unstyled-form.png -------------------------------------------------------------------------------- /guides/vanilla-with-express/images/completed-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitovi/ai-component-paste/HEAD/guides/vanilla-with-express/images/completed-project.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /src/shared/types.ts: -------------------------------------------------------------------------------- 1 | interface BaseField { 2 | name: string; 3 | type: string; 4 | description?: string | null; 5 | } 6 | type InputField = BaseField; 7 | 8 | export interface SelectField extends BaseField { 9 | options: Array; 10 | } 11 | 12 | export type FormField = InputField | SelectField; 13 | 14 | export type FormValue = string | boolean | string[] | null; 15 | 16 | export type FormElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement; 17 | -------------------------------------------------------------------------------- /vite.config.component.ts: -------------------------------------------------------------------------------- 1 | // vite.config.component.ts 2 | import { defineConfig } from "vite"; 3 | import dts from "vite-plugin-dts"; 4 | import path from "path"; 5 | 6 | export default defineConfig({ 7 | build: { 8 | lib: { 9 | entry: path.resolve(__dirname, "src/component/index.ts"), 10 | name: "component", 11 | fileName: () => "index.js", 12 | formats: ["es"], 13 | }, 14 | outDir: "dist/component", 15 | }, 16 | plugins: [ 17 | dts({ 18 | outDir: "dist", 19 | }), 20 | ], 21 | }); 22 | -------------------------------------------------------------------------------- /vite.config.extractor.ts: -------------------------------------------------------------------------------- 1 | // vite.config.extractor.ts 2 | import { defineConfig } from "vite"; 3 | import dts from "vite-plugin-dts"; 4 | import path from "path"; 5 | 6 | export default defineConfig({ 7 | build: { 8 | lib: { 9 | entry: path.resolve(__dirname, "src/extractor/index.ts"), 10 | name: "extractor", 11 | fileName: () => "index.js", 12 | formats: ["es"], 13 | }, 14 | outDir: "dist/extractor", 15 | emptyOutDir: false, 16 | }, 17 | plugins: [ 18 | dts({ 19 | outDir: "dist", 20 | }), 21 | ], 22 | }); 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedSideEffectImports": true 22 | }, 23 | "include": ["src"] 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bitovi/ai-component-paste", 3 | "version": "0.0.5", 4 | "type": "module", 5 | "files": [ 6 | "/dist" 7 | ], 8 | "exports": { 9 | "./component": { 10 | "import": "./dist/component/index.js", 11 | "types": "./dist/component/index.d.ts" 12 | }, 13 | "./extractor": { 14 | "import": "./dist/extractor/index.js", 15 | "types": "./dist/extractor/index.d.ts" 16 | } 17 | }, 18 | "scripts": { 19 | "dev": "vite", 20 | "build": "tsc && vite build --config ./vite.config.extractor.ts && vite build --config ./vite.config.component.ts", 21 | "preview": "vite preview" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "^22.14.0", 25 | "typescript": "~5.7.2", 26 | "vite": "^6.2.0", 27 | "vite-plugin-dts": "^4.5.3" 28 | }, 29 | "dependencies": { 30 | "@ai-sdk/openai": "^1.3.10", 31 | "ai": "^4.3.4", 32 | "zod": "^3.24.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/component/utils.ts: -------------------------------------------------------------------------------- 1 | import { FormElement, FormField, FormValue, SelectField } from "../shared/types"; 2 | 3 | const inputTypesToIgnore = ["button", "submit", "image", "reset", "file", "password"]; 4 | 5 | export const getFormFields = (elements: FormElement[]): FormField[] => { 6 | return elements 7 | .filter(({ name, type }) => name && !inputTypesToIgnore.includes(type)) 8 | .map((element) => { 9 | const additionalSelectFields: Partial = {}; 10 | 11 | if ("options" in element) { 12 | additionalSelectFields.options = Array.from(element.options).map(({ value }) => value); 13 | } 14 | 15 | return { 16 | name: element.name, 17 | type: element.type || "text", 18 | description: element?.getAttribute("data-sp-description"), 19 | ...additionalSelectFields, 20 | }; 21 | }); 22 | }; 23 | 24 | const isCheckbox = (element: FormElement): element is HTMLInputElement => { 25 | return element.type === "checkbox"; 26 | }; 27 | 28 | export const updateUncontrolledForm = ( 29 | elements: FormElement[], 30 | extractedFormValues: Record 31 | ): void => { 32 | for (const element of elements) { 33 | if (element.name in extractedFormValues) { 34 | const value = extractedFormValues[element.name]; 35 | 36 | if (isCheckbox(element)) { 37 | element.checked = Boolean(value); 38 | } else { 39 | element.value = String(value); 40 | } 41 | } 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /src/extractor/index.ts: -------------------------------------------------------------------------------- 1 | import { z, ZodRawShape } from "zod"; 2 | import { generateObject } from "ai"; 3 | import { openai } from "@ai-sdk/openai"; 4 | import { FormField, FormValue } from "../shared/types"; 5 | 6 | export type { FormField } from "../shared/types"; 7 | 8 | const gpt = openai("gpt-4-turbo"); 9 | 10 | function toZod(field: FormField) { 11 | if ("options" in field) { 12 | return z.enum(field.options as [string, ...string[]]).optional(); 13 | } 14 | 15 | switch (field.type) { 16 | case "number": 17 | return z.coerce.number().optional(); 18 | case "checkbox": 19 | return z.boolean().optional(); 20 | case "email": 21 | return z.string().email().optional(); 22 | case "date": 23 | return z.string().date().optional(); 24 | default: 25 | return z.string().optional(); 26 | } 27 | } 28 | 29 | function buildZodSchema(fields: FormField[]) { 30 | const shape: ZodRawShape = {}; 31 | for (const field of fields) { 32 | let base = toZod(field); 33 | if (field?.description) { 34 | base = base.describe(field.description); 35 | } 36 | shape[field.name] = base; 37 | } 38 | 39 | return z.object(shape); 40 | } 41 | 42 | export const extractFormData = async (text: string, fields: FormField[]): Promise> => { 43 | const schema = buildZodSchema(fields); 44 | 45 | const prompt = ` 46 | Given the following unstructured text: 47 | 48 | """ 49 | ${text} 50 | """ 51 | 52 | Extract the fields as a JSON object. If you do not have enough information to 53 | fill out part of the object, leave it as undefined. 54 | 55 | Only output a valid JSON object. Do not include explanation or comments. 56 | `; 57 | 58 | const { object } = await generateObject({ 59 | model: gpt, 60 | prompt, 61 | schema, 62 | }); 63 | 64 | return object; 65 | }; 66 | -------------------------------------------------------------------------------- /src/component/index.ts: -------------------------------------------------------------------------------- 1 | import { FormElement } from "../shared/types"; 2 | import { getFormFields, updateUncontrolledForm } from "./utils"; 3 | 4 | export class AIPaste extends HTMLElement { 5 | private apiUrl: string | null = null; 6 | private button!: HTMLButtonElement; 7 | private isExecuting = false; 8 | 9 | set api(value: string) { 10 | this.apiUrl = value; 11 | } 12 | 13 | get api(): string | null { 14 | return this.apiUrl; 15 | } 16 | 17 | static observedAttributes = ["api"]; 18 | 19 | attributeChangedCallback(name: string, _oldVal: string, newVal: string) { 20 | if (name === "api") { 21 | this.apiUrl = newVal; 22 | } 23 | } 24 | 25 | connectedCallback() { 26 | this.render(); 27 | } 28 | 29 | private render() { 30 | this.innerHTML = ` 31 | 34 | `; 35 | 36 | this.button = this.querySelector("button")!; 37 | this.addEventListener("click", (event) => this.handleClick(event)); 38 | } 39 | 40 | private async handleClick(event: MouseEvent) { 41 | if (this.isExecuting) return; 42 | 43 | const form = (event.currentTarget as HTMLElement).closest("form"); 44 | 45 | if (!form) { 46 | this.dispatchError(new Error("No form found.")); 47 | return; 48 | } 49 | 50 | if (!this.apiUrl) { 51 | this.dispatchError(new Error("API has not been set")); 52 | return; 53 | } 54 | 55 | this.setExecuting(true); 56 | 57 | try { 58 | const clipboardText = await navigator.clipboard.readText(); 59 | 60 | const elements = Array.from(form.elements).filter( 61 | (element): element is FormElement => 62 | element.tagName === "INPUT" || element.tagName === "SELECT" || element.tagName === "TEXTAREA" 63 | ); 64 | 65 | const fields = getFormFields(elements); 66 | 67 | const response = await fetch(this.apiUrl, { 68 | method: "POST", 69 | headers: { "Content-Type": "application/json" }, 70 | body: JSON.stringify({ text: clipboardText, fields }), 71 | }); 72 | 73 | if (!response.ok) { 74 | throw new Error("Failed to extract data."); 75 | } 76 | 77 | const extracted = await response.json(); 78 | 79 | this.dispatchEvent( 80 | new CustomEvent("ai-paste-extracted", { 81 | detail: extracted, 82 | bubbles: true, 83 | }) 84 | ); 85 | 86 | updateUncontrolledForm(elements, extracted); 87 | } catch (error) { 88 | this.dispatchError(error instanceof Error ? error : new Error("Something went wrong")); 89 | } finally { 90 | this.setExecuting(false); 91 | } 92 | } 93 | 94 | private setExecuting(isExecuting: boolean) { 95 | this.isExecuting = isExecuting; 96 | this.button.disabled = isExecuting; 97 | this.button.textContent = isExecuting ? "Smart Pasting…" : "Smart Paste"; 98 | } 99 | 100 | private dispatchError(error: Error) { 101 | console.error(error); 102 | 103 | this.dispatchEvent( 104 | new CustomEvent("ai-paste-error", { 105 | detail: error, 106 | bubbles: true, 107 | }) 108 | ); 109 | } 110 | } 111 | 112 | customElements.define("ai-paste", AIPaste); 113 | -------------------------------------------------------------------------------- /guides/open-ai-key/README.md: -------------------------------------------------------------------------------- 1 |
2 |

Creating an OpenAI Key

3 |
4 | 5 | > [!Warning] 6 | > 7 | > 🚧 Under Construction 🚧 8 | 9 | ## Overview 10 | 11 | This guide walks you through the process of creating and configuring an OpenAI API key for use with `@bitovi/ai-component-paste`. You'll learn how to set up an OpenAI account, create an organization, generate an API key, and properly configure it in your application. 12 | 13 | This guide offers two paths: 14 | 15 | - **New OpenAI Users**: If you don't have an OpenAI account yet, follow the guide from the beginning. 16 | - **Existing OpenAI Users**: If you already have an OpenAI account, you can [skip to the "With a Previous Existing OpenAI Account" section](#with-a-previous-existing-openai-account). 17 | 18 | By following these steps, you'll have everything needed to power the AI capabilities of the component and enable intelligent form filling in your projects. 19 | 20 | ## New OpenAI Users 21 | 22 | ### Creating an Account 23 | 24 | First, [create an OpenAI account](https://auth.openai.com/create-account) 25 | 26 |
27 | Create OpenAI Account 28 |
29 | 30 | Once logged in, click "Start building" 31 | 32 |
33 | Start Building 34 |
35 | 36 | ### Setting Up Your Organization 37 | 38 | Next, you'll be guided through a setup wizard where you'll first create an organization. In OpenAI, an organization serves as a workspace where you can manage API keys, track usage, and organize your AI projects. 39 | 40 |
41 | Create Organization 42 |
43 | 44 | You can name your organization whatever you want and fill in your own level of technicality. Once filled out, click "Create Organization" 45 | 46 |
47 | Create Organization Form 48 |
49 | 50 | Next it will ask if you want to invite your team. This can be skipped by clicking the "I'll invite my team later" button. 51 | 52 |
53 | Invite Team 54 |
55 | 56 | ### Generating an API Key 57 | 58 | Then it will ask you to create and generate a project and key, fill out the form giving the API key and project a name and click "Generate API Key". 59 | 60 |
61 | Key and Project 62 |
63 | 64 | The wizard will give you your key, copy it and place it in your `.env` file keyed by `OPENAI_API_KEY` 65 | 66 |
67 | Copy Key 68 |
69 | 70 | ### Configuring Your Application 71 | 72 | Once you have your API key, you need to add it to your **backend application**'s environment variables: 73 | 74 | > [!IMPORTANT] 75 | > Never commit your API key to version control. Always use environment variables or a secure secrets management system. 76 | 77 | ```bash 78 | # .env file 79 | OPENAI_API_KEY=sk-your-copied-key-here 80 | ``` 81 | 82 | ### Buying Credits 83 | 84 | Next, you'll need to select how many API credits you'd like to purchase. The recommended amount will work fine for this project. Click "Purchase Credits" to proceed. 85 | 86 |
87 | Purchase Credits 88 |
89 | 90 | This will open a payment modal. Fill out your payment information and click "Add payment method". 91 | 92 |
93 | Payment Form 94 |
95 | 96 | With that, you are all set to fully leverage `@bitovi/ai-component-paste`! 97 | 98 | ## With a Previous Existing OpenAI Account 99 | 100 | If you already have an OpenAI account, visit [https://platform.openai.com/api-keys](https://platform.openai.com/api-keys) and select the project you'd like to create the key for. 101 | 102 |
103 | OpenAI Dashboard 104 |
105 | 106 | Then click "Create new secret key". 107 | 108 |
109 | Create New Secret Key Button 110 |
111 | 112 | Name your key with something descriptive. 113 | 114 |
115 | Name New Key 116 |
117 | 118 | And click "Create secret key". 119 | 120 |
121 | Create Secret Key Button 122 |
123 | 124 | Copy your new secret key. Note that you won't be able to view this key again after closing this window. 125 | 126 |
127 | Copy New Key 128 |
129 | 130 | ### Configuring Your Application 131 | 132 | Once you have your API key, you need to add it to your **backend application**'s environment variables: 133 | 134 | > [!IMPORTANT] 135 | > Never commit your API key to version control. Always use environment variables or a secure secrets management system. 136 | 137 | ```bash 138 | # .env file 139 | OPENAI_API_KEY=sk-your-copied-key-here 140 | ``` 141 | 142 | With that, you are all set to fully leverage `@bitovi/ai-component-paste`! 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

AI Component Paste

3 |

Intelligent, AI-powered form filling with a simple copy and paste

4 |
5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |

⏱️ Save Time

Eliminate tedious manual data entry

🎯 Reduce Errors

AI-powered accuracy in form filling

😌 Better UX

Smoother, faster user experience

🔌 Easy to Add

Just a few lines of code to integrate

🧩 Flexible

Works with any form structure

🛠️ Developer Friendly

TypeScript + modern web standards
19 |
20 | 21 | ## Overview 22 | 23 | AI Component Paste solves a common frustration, manually transferring data from one source to web forms, in three simple steps: 24 | 25 | 1. **Copy any text** containing relevant information (email, address, contact details, etc.) 26 | 2. **Click the AI Paste button** in your form 27 | 3. **Watch as fields automatically populate** with the correct information 28 | 29 | Behind the scenes, this web component extracts text from your clipboard and sends it to your server. There, our extractor function leverages OpenAI's GPT models to intelligently parse the data and match it to your form fields—creating a seamless, error-free data entry experience. 30 | 31 | **[Try the live demo](https://ai-component-paste.bitovi-sandbox.com/)** to see it in action. 32 | 33 | ## Installation 34 | 35 | ```bash 36 | npm install @bitovi/ai-component-paste 37 | ``` 38 | 39 | ## Usage 40 | 41 | > [!NOTE] 42 | > 43 | > **Looking for Something a Little More Hands On? Checkout our Guides** 44 | > 45 | > - [Vanilla JS + Express](./guides/vanilla-with-express) 46 | 47 | To use AI Component Paste, you'll need three things: 48 | 49 | 1. **Frontend** An HTML form that includes the component and the client-side script. 50 | 51 | 2. **AI Integration** An OpenAI API key 52 | 53 | 3. **Backend** An API endpoint that accepts clipboard text and field metadata, and returns structured data. 54 | 55 | ### Frontend Setup 56 | 57 | Add the web component inside any HTML form you want to support smart paste on. 58 | 59 | **Include the script** 60 | If you're not using a bundler, import the component directly from [unpkg](https://unpkg.com/) 61 | 62 | ```html 63 | 64 | ``` 65 | 66 | If you're using a bundler like Vite, import the module: 67 | 68 | ```ts 69 | import "@bitovi/ai-component-paste/component"; 70 | ``` 71 | 72 | Add it to a `
`. Form inputs must have a `name` attribute defined for `ai-component-paste` to properly scrape the form. 73 | 74 | ```html 75 | 76 | 80 | 81 | 85 | 86 | 93 | 94 | 95 |
96 | ``` 97 | 98 | Once clicked, `ai-paste` will: 99 | 100 | 1. Scrape visible form fields 101 | 2. Read the clipboard text 102 | 3. Send both to your `/extractor` endpoint 103 | 4. Automatically populate the form with AI-generated values 104 | 105 | ### Backend Setup 106 | 107 | > You must run a server — this cannot be done from the frontend. 108 | > 109 | > 🔐 You must set the `OPENAI_API_KEY` as an environment variable on your server. For more on how to create an OpenAI key, [see our guide](./guides/open-ai-key). 110 | 111 | To power the AI form-filling, you'll need to set up a backend server that handles requests from ``. 112 | 113 | This endpoint should: 114 | 115 | 1. Accept clipboard text and field metadata 116 | 2. Call `extractFormData` from `@bitovi/ai-component-paste/extractor` 117 | 3. Return a key-value map of field names to values 118 | 119 | ```ts 120 | import express from "express"; 121 | 122 | import type { FormField } from "@bitovi/ai-component-paste/extractor"; 123 | import { extractFormData } from "@bitovi/ai-component-paste/extractor"; 124 | 125 | const app = express(); 126 | app.use(express.json()); 127 | 128 | app.post<{}, {}, { text: string; fields: FormField[] }>("/extractor", async (req, res) => { 129 | const { text, fields } = req.body; 130 | 131 | const result = await extractFormData(text, fields); 132 | 133 | res.json(result); 134 | }); 135 | 136 | app.listen(3000, () => { 137 | console.log("Extractor API running on http://localhost:3000"); 138 | }); 139 | ``` 140 | 141 | `extractFormData` handles formatting the request and parsing the result — you don't need to write any prompt engineering logic yourself. Make sure your environment includes: 142 | 143 | ``` 144 | OPENAI_API_KEY=sk-... 145 | ``` 146 | 147 | Once your endpoint is live, set the api attribute in your frontend form's to point to it: 148 | 149 | ```html 150 | 151 | ``` 152 | 153 | ## Events 154 | 155 | The component emits custom DOM events you can listen to for logging, analytics, or extending behavior. These events bubble, so you can attach listeners higher up the DOM tree if needed. 156 | 157 | > [!NOTE] 158 | > If you are using a **Controlled Form** with React you will need to listen to the `ai-paste-extracted` event to update your form state. 159 | 160 | `ai-paste-extracted` 161 | 162 | ```ts 163 | document.querySelector("ai-paste").addEventListener("ai-paste-extracted", (event) => { 164 | const extractedData = event.detail; 165 | 166 | console.log("Extracted data:", extractedData); 167 | }); 168 | ``` 169 | 170 | `ai-paste-error` 171 | Fired when an error occurs during extraction (e.g. network failure, invalid API key, etc.). 172 | 173 | ```ts 174 | document.querySelector("ai-paste").addEventListener("ai-paste-error", (event) => { 175 | const error = event.detail; 176 | console.error("Error:", error); 177 | }); 178 | ``` 179 | -------------------------------------------------------------------------------- /guides/vanilla-with-express/README.md: -------------------------------------------------------------------------------- 1 |
2 |

Using AI Component Paste with Vanilla and Express

3 |
4 | 5 | > [!Warning] 6 | > 7 | > 🚧 Under Construction 🚧 8 | 9 | ## Overview 10 | 11 | This guide demonstrates how to integrate `@bitovi/ai-component-paste` with a Vanilla and Express application to build an intelligent Job Post form. You'll learn how to create a form that leverages AI to automatically extract and categorize job posting information from copied text. By the end of this tutorial, you'll have a functional form that allows users to simply copy text from any source (slack message, email, etc.) and fill out a form to create the job posting. 12 | 13 |
14 | Completed Project 15 |
16 | 17 | ## The Backend 18 | 19 | We'll begin by setting up the backend of our application using Express and TypeScript. This server will host the AI extraction endpoint used by the `` component to parse and map clipboard text into form field values. 20 | 21 | ### Project Configuration 22 | 23 | To get started we will bootstrap an express application with TypeScript. 24 | 25 | ```sh 26 | mkdir ai-component-paste-be 27 | cd ai-component-paste-be 28 | npm init -y 29 | npm install express cors 30 | npm install typescript tsx @types/node @types/express @types/cors --save-dev 31 | ``` 32 | 33 | Next we'll need to create a `tsconfig.json` file that's setup for a node environment, this can be done by running 34 | 35 | ```sh 36 | npx tsc --init 37 | 38 | ``` 39 | 40 | and replacing the content of the newly create `tsconfig.json` file with 41 | 42 | ```json 43 | { 44 | "compilerOptions": { 45 | "target": "ESNext", 46 | "module": "ESNext", 47 | "moduleResolution": "Bundler", 48 | "outDir": "./dist", 49 | "strict": true, 50 | "esModuleInterop": true, 51 | "skipLibCheck": true, 52 | "forceConsistentCasingInFileNames": true 53 | }, 54 | "include": ["src/**/*.ts"], 55 | "exclude": ["node_modules"] 56 | } 57 | ``` 58 | 59 | Next, update the `package.json` to include scripts for building and running your application. You can remove the default `"test"` script and replace the entire `"scripts"` section with the following: 60 | 61 | ```diff 62 | { 63 | // ... rest of package.json 64 | "scripts": { 65 | - "test": "echo \"Error: no test specified\" && exit 1" 66 | + "build": "tsc", 67 | + "start": "node --env-file=.env dist/index.js", 68 | + "dev": "tsx --env-file=.env src/index.ts" 69 | } 70 | } 71 | ``` 72 | 73 | Additionally, we want to set the `"type"` to `"module"`. 74 | 75 | ```json 76 | { 77 | "name": "ai-component-paste-be", 78 | "version": "1.0.0", 79 | "main": "index.js", 80 | "type": "module", 81 | "scripts": { 82 | "build": "tsc", 83 | "start": "node --env-file=.env dist/index.js", 84 | "dev": "tsx --env-file=.env src/index.ts" 85 | }, 86 | "keywords": [], 87 | "author": "", 88 | "license": "ISC", 89 | "description": "", 90 | "dependencies": { 91 | "@bitovi/ai-component-paste": "^0.0.5", 92 | "cors": "^2.8.5", 93 | "express": "^5.1.0" 94 | }, 95 | "devDependencies": { 96 | "@types/cors": "^2.8.17", 97 | "@types/express": "^5.0.1", 98 | "@types/node": "^22.14.1", 99 | "tsx": "^4.19.3", 100 | "typescript": "^5.8.3" 101 | } 102 | } 103 | ``` 104 | 105 | We need to create two files. Open the project in a code editor and create: 106 | 107 | 1. `.env` to house our environment 108 | 2. `src/index.ts` to house our server 109 | 110 | Once done, your project should look like this: 111 | 112 | ``` 113 | └── node_modules 114 | ├── src/ 115 | │ └── index.ts (this file is empty) 116 | └── .env (this file is empty) 117 | └── package.json 118 | └── package-lock.json 119 | └── tsconfig.json 120 | ``` 121 | 122 | With our project scaffolded and development scripts in place, we're ready to build the Express server that will handle AI-powered form extraction. 123 | 124 | ### Building the Server 125 | 126 | Next, we'll need to create the server itself. Open the `src/index.ts` file and add the following code: 127 | 128 | ```ts 129 | import express, { Request, Response } from "express"; 130 | import cors from "cors"; 131 | 132 | const app = express(); 133 | const port = 3000; 134 | 135 | // The frontend will run on port :5173 136 | app.use(cors({ origin: ["http://localhost:5173"] })); 137 | app.use(express.json()); 138 | 139 | app.get("/", (req: Request, res: Response) => { 140 | res.send("Hello World!"); 141 | }); 142 | 143 | app.listen(port, () => { 144 | console.log(`@bitovi/ai-component-paste example app listening on port ${port}`); 145 | }); 146 | ``` 147 | 148 | You can verify your setup is correct by running: 149 | 150 | ```sh 151 | npm run dev 152 | ``` 153 | 154 | you should see a `"@bitovi/ai-component-paste example app listening on port 3000"` in the terminal and if you visit `http://localhost:3000` in your browser you should see "Hello World!". Once verified you can delete the `app.get("/", ...)` from `src/index.ts` 155 | 156 | ```diff 157 | 158 | import express, { Request, Response } from "express"; 159 | import cors from "cors"; 160 | 161 | const app = express(); 162 | const port = 3000; 163 | 164 | app.use(cors({ origin: ["http://localhost:5173"] })); 165 | app.use(express.json()); 166 | 167 | -app.get("/", (req: Request, res: Response) => { 168 | - res.send("Hello World!"); 169 | -}); 170 | 171 | app.listen(port, () => { 172 | console.log(`@bitovi/ai-component-paste example app listening on port ${port}`); 173 | }); 174 | ``` 175 | 176 | Now that we've confirmed our server is up and running, let's integrate the AI extraction logic using `@bitovi/ai-component-paste`. 177 | 178 | ### Integrating `@bitovi/ai-component-paste` 179 | 180 | Next we can install and use `@bitovi/ai-component-paste` 181 | 182 | ```sh 183 | npm install @bitovi/ai-component-paste 184 | ``` 185 | 186 | We'll also need to configure our environment. In the `.env` file you created earlier, add your OpenAI API key: 187 | 188 | > [!NOTE] 189 | > 190 | > Need help creating an OpenAI key? Check out [our guide to getting one](../open-ai-key) 191 | > to help you get set up. 192 | 193 | ``` 194 | OPENAI_API_KEY=your-openai-key-here 195 | ``` 196 | 197 | > [!WARNING] 198 | > 199 | > We aren't using git in this guide, but if you are, make sure not to 200 | > commit this file to version control. 201 | 202 | ### Creating the Extraction Endpoint 203 | 204 | Next, we'll define a `POST` endpoint that the `` component can call. This endpoint will receive clipboard text along with scraped form field metadata and return the AI-generated form values. 205 | 206 | We'll call this route `/extract-form-data` and the request body should include: 207 | 208 | - **text** - the clipboard content 209 | - **fields** - the list of form fields scraped from the page 210 | 211 | ```ts 212 | import type { FormField } from "@bitovi/ai-component-paste/extractor"; 213 | 214 | interface AIComponentPasteRequestBody { 215 | text: string; 216 | fields: FormField[]; 217 | } 218 | ``` 219 | 220 | With the request shape defined, let's build the Express route that will handle incoming data from ``, pass it to the AI model, and return the structured result. `extractFormData` takes care of calling the LLM and returning structured values that match your form fields. 221 | 222 | ```ts 223 | import { extractFormData, FormField } from "@bitovi/ai-component-paste/extractor"; 224 | 225 | interface AIComponentPasteRequestBody { 226 | text: string; 227 | fields: FormField[]; 228 | } 229 | 230 | // other parts of the file omitted for brevity 231 | 232 | app.post("/extract-form-data", async (req: Request<{}, {}, AIComponentPasteRequestBody>, res: Response) => { 233 | const { text, fields } = req.body; 234 | 235 | try { 236 | const result = await extractFormData(text, fields); 237 | 238 | res.json(result); 239 | } catch (error) { 240 | console.error("Error extracting form data:", error); 241 | res.status(500).json({ error: "Failed to extract form data" }); 242 | } 243 | }); 244 | 245 | // other parts of the file omitted for brevity 246 | ``` 247 | 248 | With the backend in place and ready to handle extraction requests, we can now move on to building the frontend form and wiring up the `` component. 249 | 250 | ## The Frontend 251 | 252 | We'll use [Vite](https://vitejs.dev/) to scaffold a lightweight frontend project using plain HTML and TypeScript. This will serve the form that includes the `` component. 253 | 254 | Start by creating a new Vite project: 255 | 256 | ```sh 257 | npm create vite@latest ai-component-paste-fe 258 | ``` 259 | 260 | When prompted, choose: 261 | 262 | - Framework: `Vanilla` 263 | - Variant: `TypeScript` 264 | 265 | Then install your dependencies: 266 | 267 | ```sh 268 | cd ai-component-paste-fe 269 | npm install 270 | ``` 271 | 272 | ### Cleaning Up the Starter Files 273 | 274 | Before we add our custom code, let's remove the boilerplate that comes with Vite. Open the project in your editor and: 275 | 276 | 1. Clear out `main.ts` - Open `src/main.ts` and delete everything inside. We'll replace this with an import of the `@bitovi/ai-component-paste` package in a later step. 277 | 278 | 2. Clear out `styles.css` - Open `src/styles.css` and delete everything inside. While we're getting the component added, were going to avoid styling, at the end, html with the needed tailwind class names will be provided 279 | 280 | 3. Delete `counter.ts` - You won't need the sample counter functionality. 281 | 282 | 4. Delete `typescript.svg` - You won't need the TypeScript logo. 283 | 284 | With the boilerplate removed, your frontend project is now ready for the form and AI integration. 285 | 286 | ### Adding the Form 287 | 288 | Now we can update the contents of `index.html` to include the job post form. Inside the `
`, add the following markup: 289 | 290 | ```html 291 |
292 |
293 |
294 | 295 | 296 |
297 | 298 |
299 | 300 | 301 |
302 | 303 |
304 | 305 | 306 |
307 | 308 |
309 | 310 | 317 |
318 | 319 |
320 | 321 | 322 |
323 | 324 |
325 | 326 | 331 |
332 | 333 |
334 | 335 | 336 |
337 | 338 |
339 | 340 | 345 |
346 |
347 | 348 |
349 | 350 |
351 |
352 | ``` 353 | 354 | Once added, you should be able to start the dev sever: 355 | 356 | ```sh 357 | npm run dev 358 | ``` 359 | 360 | Then navigate to `http://localhost:5173` — you should see an unstyled form rendered in the browser. 361 | 362 |
363 | Unstyled Form 364 |
365 | 366 | ### Integrating `@bitovi/ai-component-paste` 367 | 368 | Now let's bring the form to life by wiring up the `` component so it can scrape the form, read the clipboard, and call our backend. 369 | 370 | First, install the component package: 371 | 372 | ```sh 373 | npm install @bitovi/ai-component-paste 374 | ``` 375 | 376 | Then open `src/main.ts` and add the following import to register the web component: 377 | 378 | ```ts 379 | import "@bitovi/ai-component-paste/component"; 380 | ``` 381 | 382 | Now, Add next to the "Post Job" button and set the `api` attribute to your backend extraction endpoint: 383 | 384 | ```html 385 |
386 | 387 | 388 |
389 | ``` 390 | 391 |
392 | Unstyled Form With AI Paste 393 |
394 | 395 | That's it! When users paste text into the page and click the AI Paste button, the component will: 396 | 397 | - Scrape the form fields 398 | - Read clipboard contents 399 | - Send everything to your Express backend 400 | - Fill in matching fields with AI-generated values 401 | 402 | ## Running the Full App 403 | 404 | Make sure both the frontend and backend projects are running. From the `ai-component-paste-be directory`: 405 | 406 | ```sh 407 | npm run dev 408 | ``` 409 | 410 | From the `ai-component-paste-fe directory`: 411 | 412 | ```sh 413 | npm run dev 414 | ``` 415 | 416 | Once both are running, copy the text below and click the AI Paste button in your form: 417 | 418 | ``` 419 | We're hiring a Senior Frontend Developer in San Francisco, CA. The role is full-time and remote friendly, with a salary range between $120,000 and $140,000. Ideally, the candidate starts on July 1st. They can reach out to hiring@coolstartup.com with any questions. This position is part of the engineering department and includes building modern, performant UIs. 420 | ``` 421 | 422 |
423 | Unstyled Form With AI Paste Highlighted 424 |
425 | 426 | Once pasted the form should be filled out, AI is non-deterministic so it might not be the same as the image below, but it should look similar 427 | 428 |
429 | Working Form with AI-filled Data 430 |
431 | 432 | ## Improvements to the App 433 | 434 | At this point, you have a working integration! If you're just looking for a basic copy-to-form experience, you're good to go. 435 | 436 | However, if you want to level up your app with better error handling, helpful field descriptions, and basic styling — read on. 437 | 438 | ## Error Handling 439 | 440 | If something goes wrong during extraction — like a network issue or a missing API key — the `` component will emit a custom `ai-paste-error` event container an `Error` in the `detail`. 441 | 442 | You can listen for this event to surface meaningful feedback to users. For example, let's display a simple alert when something fails. 443 | 444 | Open `src/main.ts` in the frontend project and add an event listener on ``. 445 | 446 | ```ts 447 | const isAIPasteError = (event: Event): event is CustomEvent => { 448 | return "detail" in event && event.detail instanceof Error; 449 | }; 450 | 451 | document.querySelector("ai-paste")?.addEventListener("ai-paste-error", (event) => { 452 | if (isAIPasteError(event)) { 453 | alert(event.detail.message); 454 | } 455 | }); 456 | ``` 457 | 458 | You can test this by stopping the backend and trying to paste again. You should see an alert like. 459 | 460 |
461 | Error Message Alert 462 |
463 | 464 | ## Adding Descriptions 465 | 466 | Sometimes, a field name alone isn't enough for the AI to understand how to fill it correctly — especially for inputs like dates or open-ended fields like job descriptions. To help the model make more accurate decisions, you can add `data-sp-description` attributes to your form fields. These descriptions are sent to the AI as context and can dramatically improve extraction accuracy. 467 | 468 | For the start date, you can clarify the expected format by updating the `` in `index.html`. 469 | 470 | ```html 471 | 472 | ``` 473 | 474 | For larger, freeform fields, you can help the AI understand what kind of content to generate. For example, we can update the ` 482 | ``` 483 | 484 | ## Styling 485 | 486 | To improve the look and feel of the form (and to match the screenshot at the beginning), we can add Tailwind CSS to the frontend project. In the frontend project run: 487 | 488 | ```sh 489 | npm install tailwindcss @tailwindcss/vite 490 | ``` 491 | 492 | Then create a `vite.config.ts` file at the root of the project and add the following code. 493 | 494 | ```ts 495 | import { defineConfig } from "vite"; 496 | import tailwindcss from "@tailwindcss/vite"; 497 | 498 | export default defineConfig({ 499 | plugins: [tailwindcss()], 500 | }); 501 | ``` 502 | 503 | Next, in the empty `src/style.css` add the following tailwind directive. 504 | 505 | ```css 506 | @import "tailwindcss"; 507 | ``` 508 | 509 | Finally, we need to import our css. This has to be done in two places. First, At the top of `src/main.ts` 510 | 511 | ```diff 512 | import "@bitovi/ai-component-paste/component"; 513 | + import "./styles.css"; 514 | 515 | const isAIPasteError = (event: Event): event is CustomEvent => { 516 | return "detail" in event && event.detail instanceof Error; 517 | }; 518 | 519 | document.querySelector("ai-paste")?.addEventListener("ai-paste-error", (event) => { 520 | if (isAIPasteError(event)) { 521 | alert(event.detail.message); 522 | } 523 | }); 524 | ``` 525 | 526 | Second, in the `` of `index.html`. 527 | 528 | ```diff 529 | 530 | 531 | 532 | 533 | + 534 | Vite + TS 535 | 536 | ``` 537 | 538 | With tailwind setup you can style your form however you'd like, to match the screenshot above use the following `
` 539 | 540 | ```html 541 | 542 |
543 |
544 |
545 |
546 |
547 | 548 |
549 |
550 | 551 | 557 |
558 | 559 |
560 | 561 | 567 |
568 | 569 |
570 | 571 | 577 |
578 | 579 |
580 | 581 |
582 | 593 |
594 |
595 |
596 | 597 |
598 | 599 | 606 |
607 | 608 |
609 | 610 | 620 |
621 | 622 |
623 | 624 | 631 |
632 | 633 |
634 | 635 | 642 |
643 |
644 | 645 |
646 | 649 | 650 | 656 |
657 | 658 |
659 |
Your job posting will be reviewed within 24 hours
660 |
661 | ``` 662 | 663 | That's it — you now have a fully functional, AI-powered job posting form, styled with Tailwind, backed by Express, 664 | and ready to save your users hours of manual entry. 665 | 666 |
667 | Completed Project 668 |
669 | --------------------------------------------------------------------------------