JavaScript Development Space

Create Your Own HTML5 Tag With ChatGPT Autocomplete, Text Replacement, or Translation

Add to your RSS feedSeptember, 19th 20248 min read
Create Your Own HTML5 Tag With ChatGPT Autocomplete, Text Replacement, or Translation

Among the modern HTML standards and specifications, there’s something called Custom Elements. For those unfamiliar, it's a way to create your own tags, which the browser automatically initializes when it encounters them in the markup, executing the specific behavior logic you’ve defined. Additionally, there’s a way to modify the behavior of standard tags (though the nuances of this are beyond the scope of this discussion).

In this tutorial, we will create a smart HTML tag—a text field that helps users format the text they enter. This tag can be used on any website, in any web application built with modern frameworks, or even in a simple static HTML file.

Preparatory Steps

First, let's define the technologies we'll be using. We need to initialize a project with Parcel, get familiar with the Symbiote.js library, and obtain an API key from ChatGPT. If you are not familiar with Custom Elements, please read the howto.

Install Symbiote.js

SymbioteJS is a lightweight JavaScript library designed to simplify the creation of web components and improve the development experience with custom HTML elements. Built with modern web standards in mind, it offers a simple and efficient way to structure, style, and manage reusable components, without the need for heavy frameworks.

npm i @symbiotejs/symbiote

Create a Component

Next, we’ll create our component. Since this project is quite small, we’ll implement it directly in the app.ts file.

js
1 import Symbiote, { html, css } from '@symbiotejs/symbiote';
2
3 export class SmartTextarea extends Symbiote {
4 // The object that initializes the state and core entities of the component:
5 init$ = {};
6 }
7
8 // Styles of component
9 SmartTextarea.rootStyles = css``;
10
11 // Template of component:
12 SmartTextarea.template = html``;
13
14 // Define a custom HTML tag:
15 SmartTextarea.reg('smart-textarea');

Now let's create a HTML file

html
1 <script type="importmap">
2 {
3 "imports": {
4 "@symbiotejs/symbiote": "https://esm.run/@symbiotejs/symbiote"
5 }
6 }
7 </script>
8 <script type="module" src="./smart-textarea.js"></script>
9
10 <smart-textarea model="gpt-4o-mini"></smart-textarea>

An important aspect here is the block with the import map. In our example, we will include the SymbioteJS library via CDN, which will allow us to efficiently and repeatedly share a common dependency among different independent components of the application, without the need for bulky solutions like Module Federation. Additionally, since we initially installed the dependency through NPM, we will have access to everything necessary for our development environment tools, including type declarations for TypeScript support, entity definitions, and more.

Template

Let's create a template

js
1 SmartTextarea.template = html`
2 <textarea
3 ${{ oninput: 'saveSourceText' }}
4 placeholder="AI assisted text input..."
5 ref="text"
6 ></textarea>
7
8 <input type="text" placeholder="Preferred Language" ref="lang" />
9
10 <label>Text style: {{+currentTextStyle}}</label>
11 <input
12 ${{ onchange: 'onTextStyleChange' }}
13 type="range"
14 min="1"
15 max="${textStyles.length}"
16 step="1"
17 ref="textStyleRange"
18 />
19
20 <button ${{ onclick: 'askAi' }}>Rewrite text</button>
21 <button ${{ onclick: 'revertChanges' }}>Revert AI changes</button>
22 `;

The code snippet defines a template for a SmartTextarea component using a template literal. This template describes the HTML structure of the component, along with some dynamic bindings and event handlers.

js
1 {
2 {
3 +currentTextStyle;
4 }
5 }

The plus sign (+) at the beginning of the name indicates that the property is computed, meaning it is automatically derived when the state properties change or can be manually triggered using a special method called notify.

State Entities and Handlers

Now let's describe the properties and methods that we bind to the template.

js
1 export class SmartTextarea extends Symbiote {
2 // Store the user's original text in a private class property
3 #sourceText = '';
4
5 init$ = {
6 // LLM name by default
7 '@model': 'gpt-4o',
8
9 // The computed property contains the description of the style to which we need to format our text.
10 '+currentTextStyle': () => {
11 return textStyles[this.ref.textStyleRange.value - 1];
12 },
13
14 // Save the user's text for the undo function.
15 saveSourceText: () => {
16 this.#sourceText = this.ref.text.value;
17 },
18 // Restore the textarea to the original text.
19 revertChanges: () => {
20 this.ref.text.value = this.#sourceText;
21 },
22 // Respond to text style selection.
23 onTextStyleChange: (e) => {
24 // Manually trigger the recalculation of the computed property.
25 this.notify('+currentTextStyle');
26 },
27
28 // ...
29 };
30 }

Now we need an array containing descriptions of text styles, which we will create in a separate module called textStyles.ts with the following content:

js
1 export const textStyles: string[] = [
2 'Free informal speech, jokes, memes, emoji, possibly long',
3 'Casual chat, friendly tone, occasional emoji, short and relaxed',
4 'Medium formality, soft style, basic set of emoji possible, compact',
5 'Neutral tone, clear and direct, minimal slang or emoji',
6 'Professional tone, polite and respectful, no emoji, short sentences',
7 'Strict business language. Polite and grammatically correct.',
8 'Highly formal, authoritative, extensive use of complex vocabulary, long and structured',
9 ];

Additionally, in the code above, we can see examples of how to access the elements described in the template using the ref interface, such as:

js
1 this.ref.text.value;

This is similar to how it works in React and helps avoid manually searching for elements using the DOM API. Essentially, this.ref is a collection of references to DOM elements that have the corresponding attribute set in the HTML template, such as ref="text".

Request to the LLM

Now we need to do the most important thing: ask the AI to rewrite our text according to the specified settings. In this example, I will keep it as simple as possible, without using any additional libraries or access control layers, by sending a direct request to the API:

js
1 export class SmartTextarea extends Symbiote {
2 // ...
3
4 init$ = {
5 // ...
6
7 askAi: async () => {
8 // If the textarea is empty, we cancel everything and display an alert:
9 if (!this.ref.text.value.trim()) {
10 alert('Your text input is empty');
11 return;
12 }
13
14 // We send a request to the API endpoint taken from the configuration:
15 let aiResponse = await (
16 await window.fetch(CFG.apiUrl, {
17 method: 'POST',
18 headers: {
19 'Content-Type': 'application/json',
20
21 // We retrieve the API key from a hidden JavaScript module that is not tracked by git:
22 Authorization: `Bearer ${CFG.apiKey}`,
23 },
24 body: JSON.stringify({
25 // Read the name of the required model from the HTML attribute (gpt-4o-mini),
26 // or use the default model (gpt-4o):
27 model: this.$['@model'],
28 messages: [
29 {
30 role: 'system',
31
32 // Pass the language and tone settings to the model:
33 content: JSON.stringify({
34 useLanguage: this.ref.lang.value || 'Same as the initial text language',
35 textStyle: this.$['+currentTextStyle'],
36 }),
37 },
38 {
39 role: 'assistant',
40
41 // Describe the role of the AI assistant:
42 content:
43 'You are the text writing assistant. Rewrite the input text according to parameters provided.',
44 },
45 {
46 role: 'user',
47
48 // Pass the text that we want to modify:
49 content: this.ref.text.value,
50 },
51 ],
52 temperature: 0.7,
53 }),
54 })
55 ).json();
56
57 // Wait for the response and update the text in the input field:
58 this.ref.text.value = aiResponse?.choices?.[0]?.message.content || this.ref.text.value;
59 },
60 };
61 }

Now, we need to create a configuration module (secret.ts), which we will hide from prying eyes using .gitignore:

js
1 export const CFG = {
2 apiUrl: 'https://api.openai.com/v1/chat/completions',
3 apiKey: '<YOUR_API_KEY>',
4 };

Styles

We just need to add styles to our web component.

js
1 // ...
2
3 SmartTextarea.rootStyles = css`
4 smart-textarea {
5 display: inline-flex;
6 flex-flow: column;
7 gap: 10px;
8 width: 500px;
9
10 textarea {
11 width: 100%;
12 height: 200px;
13 }
14 }
15 `;
16
17 // ...

Full code

js
1 import Symbiote, { html, css } from '@symbiotejs/symbiote';
2 import { CFG } from './secret.js';
3 import { textStyles } from './textStyles.js';
4
5 export class SmartTextarea extends Symbiote {
6 #sourceText = '';
7
8 init$ = {
9 '@model': 'gpt-4o',
10
11 '+currentTextStyle': () => {
12 return textStyles[this.ref.textStyleRange.value - 1];
13 },
14
15 saveSourceText: () => {
16 this.#sourceText = this.ref.text.value;
17 },
18 revertChanges: () => {
19 this.ref.text.value = this.#sourceText;
20 },
21 onTextStyleChange: (e) => {
22 this.notify('+currentTextStyle');
23 },
24 askAi: async () => {
25 if (!this.ref.text.value.trim()) {
26 alert('Your text input is empty');
27 return;
28 }
29 let aiResponse = await (
30 await window.fetch(CFG.apiUrl, {
31 method: 'POST',
32 headers: {
33 'Content-Type': 'application/json',
34 Authorization: `Bearer ${CFG.apiKey}`,
35 },
36 body: JSON.stringify({
37 model: this.$['@model'],
38 messages: [
39 {
40 role: 'system',
41 content: JSON.stringify({
42 useLanguage: this.ref.lang.value || 'Same as the initial text language',
43 textStyle: this.$['+currentTextStyle'],
44 }),
45 },
46 {
47 role: 'assistant',
48 content:
49 'You are the text writing assistant. Rewrite the input text according to parameters provided.',
50 },
51 {
52 role: 'user',
53 content: this.ref.text.value,
54 },
55 ],
56 temperature: 0.7,
57 }),
58 })
59 ).json();
60
61 this.ref.text.value = aiResponse?.choices?.[0]?.message.content || this.ref.text.value;
62 },
63 };
64 }
65
66 SmartTextarea.rootStyles = css`
67 smart-textarea {
68 display: inline-flex;
69 flex-flow: column;
70 gap: 10px;
71 width: 500px;
72
73 textarea {
74 width: 100%;
75 height: 200px;
76 }
77 }
78 `;
79
80 SmartTextarea.template = html`
81 <textarea
82 ${{ oninput: 'saveSourceText' }}
83 placeholder="AI assisted text input..."
84 ref="text"
85 ></textarea>
86
87 <input type="text" placeholder="Preferred Language" ref="lang" />
88
89 <label>Text style: {{+currentTextStyle}}</label>
90 <input
91 ${{ onchange: 'onTextStyleChange' }}
92 type="range"
93 min="1"
94 max="${textStyles.length}"
95 step="1"
96 ref="textStyleRange"
97 />
98
99 <button ${{ onclick: 'askAi' }}>Rewrite text</button>
100 <button ${{ onclick: 'revertChanges' }}>Revert AI changes</button>
101 `;
102
103 SmartTextarea.reg('smart-textarea');

This template sets up a user interface that allows users to input text, specify a preferred language, adjust the style of the text using a range input, and interact with an AI for rewriting text. The use of ref and event bindings provides a way to interact with these elements programmatically within the SmartTextarea component, making it dynamic and responsive to user actions.

Now, run:

npm run dev
HTML5 Tag With ChatGPT

Now we can use the tag in the templates of other components built with any modern frameworks; in markup generated on the server using any templating engine or static site generator, in simple HTML files with forms, and so on.

References

Docs Habr

Related Posts:

The Best Way to Create Singleton Design Pattern in JavaScript and TypeScript

Discover the best practices for implementing the Singleton Design Pattern in JavaScript and TypeScript. This guide covers step-by-step instructions to ensure efficient and scalable code.

Gulp 4 Crash Course - Installation, Setup and Launch

Gulp is a popular task runner built on Node.js that automates repetitive tasks in your development workflow. I'll guide you through the installation, setup, and launching of Gulp 4.

JavaScript Development Space

Follow JavaScript Development Space

Stay updated with the latest trends, tutorials, and best practices in JavaScript development. Follow our JavaScript Development blog for expert insights, coding tips, and resources to enhance your web development skills.

© 2024 JavaScript Development Blog. All rights reserved.