Adventures in AI Land

How ChatGPT Became My Code-Whispering Sidekick on a new Coalcracker Quest.

Lately, there’s been a buzz around AI technology like OpenAI’s ChatGPT and Google’s Bard that’s got people more excited than a tech nerd at a smartphone launch. As a curious tech aficionado, I’ve been tinkering with ChatGPT 3.5 for a hot minute. I recently signed up for the OpenAI API, hoping to run more advanced queries against the chat and completions endpoint, and boy, did I have a lot to learn! After a weekend of trial, error, and caffeine, I decided to take the plunge and subscribe to ChatGPT Plus, which unlocks the mystical powers of the GPT-4 model.

The main goal for this tech adventure is to harness AI technology to generate code, expanding my horizons, learning new technologies, and gaining insight into what I believe is the future of computing (spoiler alert: it’s AI!).

AI technology is in its infancy, growing faster than a toddler on a sugar rush. With recent advancements, new tools are emerging that can either be your best friend or your worst enemy, depending on your perspective. As for me, I decided to dive headfirst into the AI pool and hope there’s water in it.

This weekend’s escapade involved asking ChatGPT 3.5 to help me write some ReactJS code to interact with the OpenAI API. Now, keep in mind that my experience with ReactJS was as limited as a goldfish’s attention span.

ChatGPT graciously provided some code to submit requests to the completions endpoint. After doing some Google-fu and setting up the app with NPM, I tweaked the code, plugged in my API key, and I was off like a rocket!

I asked a few test questions and ironed out minor code wrinkles. Then, I requested the API to generate some ReactJS code for other OpenAI endpoints.

My grand plan was to conjure up code for dynamic forms that could store data on my trips, compile a summarized prompt, and send it to the OpenAI API. I wanted an app with versatile forms to document restaurants, hotels, events, and activities – without being confined to specific fields.

As a PHP/MySQL veteran of 20 years, I wrestled with this challenge like a hungry programmer battling a stubborn vending machine. I scoured Google, binged on videos, and even signed up for a ReactJS course on Coursera (which, at my current pace, I’ll complete by October 2029). But I needed a quicker solution and a more tailored approach.

I considered MariaDB with Laravel or Python, as I’ve dabbled in the latter recently. However, my heart kept gravitating towards ReactJS, eager to dive into uncharted waters. The best way for me to learn is by having a project to complete and immersing myself in the technology, like a digital baptism. It worked 25+ years ago when I stumbled upon the internet and again when I mastered PHP. So, why not now? I have a project, Coalcracker.com, and a burning desire for an app to help me realize its potential. Let the tech adventure continue!

Prompt:

Please write a reactjs application that has the ability to add dynamic fields in order to create a form that can store data to local storage. It should be able to store unlimited data that I can edit by selecting the title of the stored data from a list. The dynamic forms should be able to be saved, so they can be used again. There should be the ability to categorize the form and add tags. Once a category or tag has been added on a form, they should be stored so they can be presented in a select list on future forms. It should also have the ability to add notes to a form, with the functionality needed to add multiple notes to each form with a note headline and note details. Once a note headline has been added, it should be stored so that it can be presented in a select list for future notes.

To create this ReactJS application, we’ll break down the requirements and create the components step by step. Here is the overall structure of the application:

  1. App Component
    • DynamicFormContainer Component
      • FormEditor Component
        • Field Component
      • NoteEditor Component
        • Note Component
    • StorageManager Component

App Component:

import React, { useState } from 'react';
import './App.css';
import DynamicFormContainer from './components/DynamicFormContainer';
import StorageManager from './components/StorageManager';

function App() {
  const [formsData, setFormsData] = useState([]);

  return (
    <div className="App">
      <DynamicFormContainer
        onSave={(newFormData) => {
          setFormsData([...formsData, newFormData]);
        }}
      />
      <StorageManager formsData={formsData} />
    </div>
  );
}

export default App;

DynamicFormContainer Component:

import React, { useState } from 'react';
import FormEditor from './FormEditor';
import NoteEditor from './NoteEditor';

const DynamicFormContainer = ({ onSave }) => {
  const [formFields, setFormFields] = useState([]);
  const [formNotes, setFormNotes] = useState([]);

  const handleSave = () => {
    onSave({ fields: formFields, notes: formNotes });
  };

  return (
    <div>
      <FormEditor
        onFieldsChange={(newFields) => {
          setFormFields(newFields);
        }}
      />
      <NoteEditor
        onNotesChange={(newNotes) => {
          setFormNotes(newNotes);
        }}
      />
      <button onClick={handleSave}>Save Form</button>
    </div>
  );
};

export default DynamicFormContainer;

FormEditor, Field, NoteEditor, Note, and StorageManager Components will need to be implemented to fulfill the requirements. To keep the response concise, I’ll provide the starting point for each component.

FormEditor Component:

  • Handles adding and removing dynamic fields
  • Stores field information and manages tags and categories

Field Component:

  • Represents a single field in the form

NoteEditor Component:

  • Handles adding and removing notes
  • Stores note information and manages note headlines

Note Component:

  • Represents a single note in the form

StorageManager Component:

  • Displays the list of stored forms
  • Handles editing and selecting forms

As a starting point, you can create the necessary components in separate files and import them in the main App component. You will need to manage the state, event handling, and local storage for each component based on the requirements.

Please note that the provided code is just a starting point for the application, and you will need to implement the complete functionality for each component. The components should be styled using CSS, and you can utilize external libraries such as Material-UI, Ant Design, or Bootstrap if needed.

Prompt:

Can you provide code for the formeditor component?

import React, { useState } from 'react';
import Field from './Field';

const FormEditor = ({ onFieldsChange }) => {
  const [fields, setFields] = useState([]);
  const [categories, setCategories] = useState([]);
  const [tags, setTags] = useState([]);
  const [category, setCategory] = useState('');
  const [tag, setTag] = useState('');

  const addField = () => {
    setFields([...fields, { id: Date.now(), label: '', value: '' }]);
  };

  const removeField = (id) => {
    setFields(fields.filter((field) => field.id !== id));
  };

  const handleFieldChange = (id, updatedField) => {
    const newFields = fields.map((field) =>
      field.id === id ? updatedField : field
    );
    setFields(newFields);
    onFieldsChange(newFields);
  };

  const handleCategoryChange = (e) => {
    setCategory(e.target.value);
  };

  const handleTagChange = (e) => {
    setTag(e.target.value);
  };

  const addCategory = () => {
    if (!categories.includes(category)) {
      setCategories([...categories, category]);
    }
    setCategory('');
  };

  const addTag = () => {
    if (!tags.includes(tag)) {
      setTags([...tags, tag]);
    }
    setTag('');
  };

  return (
    <div>
      <h3>Form Editor</h3>
      <div>
        <label>Category: </label>
        <input type="text" value={category} onChange={handleCategoryChange} />
        <button onClick={addCategory}>Add Category</button>
        <select>{categories.map((cat) => <option key={cat}>{cat}</option>)}</select>
      </div>
      <div>
        <label>Tag: </label>
        <input type="text" value={tag} onChange={handleTagChange} />
        <button onClick={addTag}>Add Tag</button>
        <select>{tags.map((tag) => <option key={tag}>{tag}</option>)}</select>
      </div>
      {fields.map((field) => (
        <Field
          key={field.id}
          field={field}
          onChange={(updatedField) => handleFieldChange(field.id, updatedField)}
          onRemove={() => removeField(field.id)}
        />
      ))}
      <button onClick={addField}>Add Field</button>
    </div>
  );
};

export default FormEditor;

Now, let’s create the Field component:

Field.js:

It got a little interesting here. It provided part of the script and then ended, so I asked for just this code, with the following prompt:

Prompt:

Can you provide Field.js again, please?

Apologies for the incomplete response. Here’s the complete Field.js component code:Field.js:

import React, { useState } from 'react';

const Field = ({ field, onChange, onRemove }) => {
  const [label, setLabel] = useState(field.label);
  const [value, setValue] = useState(field.value);

  const handleLabelChange = (e) => {
    setLabel(e.target.value);
    onChange({ ...field, label: e.target.value });
  };

  const handleValueChange = (e) => {
    setValue(e.target.value);
    onChange({ ...field, value: e.target.value });
  };

  return (
    <div>
      <label>
        Label:
        <input type="text" value={label} onChange={handleLabelChange} />
      </label>
      <label>
        Value:
        <input type="text" value={value} onChange={handleValueChange} />
      </label>
      <button onClick={onRemove}>Remove</button>
    </div>
  );
};

export default Field;

In this Field component, we have two input fields to manage the label and value of each field. The onChange and onRemove functions are passed from the FormEditor component, so when a field is updated or removed, the FormEditor can update its state accordingly.

Prompt:

Can you provide code for the NoteEditor component?

import React, { useState } from 'react';
import Note from './Note';

const NoteEditor = ({ onNotesChange }) => {
  const [notes, setNotes] = useState([]);
  const [noteHeadlines, setNoteHeadlines] = useState([]);
  const [selectedHeadline, setSelectedHeadline] = useState('');

  const addNote = () => {
    setNotes([
      ...notes,
      { id: Date.now(), headline: selectedHeadline, details: '' },
    ]);
  };

  const removeNote = (id) => {
    setNotes(notes.filter((note) => note.id !== id));
  };

  const handleNoteChange = (id, updatedNote) => {
    const newNotes = notes.map((note) =>
      note.id === id ? updatedNote : note
    );
    setNotes(newNotes);
    onNotesChange(newNotes);
  };

  const handleHeadlineChange = (e) => {
    setSelectedHeadline(e.target.value);
    if (!noteHeadlines.includes(e.target.value)) {
      setNoteHeadlines([...noteHeadlines, e.target.value]);
    }
  };

  return (
    <div>
      <h3>Note Editor</h3>
      <label>
        Headline:
        <select value={selectedHeadline} onChange={handleHeadlineChange}>
          <option value="">Select Headline</option>
          {noteHeadlines.map((headline) => (
            <option key={headline} value={headline}>
              {headline}
            </option>
          ))}
        </select>
      </label>
      <button onClick={addNote} disabled={!selectedHeadline}>
        Add Note
      </button>
      {notes.map((note) => (
        <Note
          key={note.id}
          note={note}
          onChange={(updatedNote) => handleNoteChange(note.id, updatedNote)}
          onRemove={() => removeNote(note.id)}
        />
      ))}
    </div>
  );
};

export default NoteEditor;

Now, let’s create the Note component:

Note.js:

import React, { useState } from 'react';

const Note = ({ note, onChange, onRemove }) => {
  const [details, setDetails] = useState(note.details);

  const handleDetailsChange = (e) => {
    setDetails(e.target.value);
    onChange({ ...note, details: e.target.value });
  };

  return (
    <div>
      <h4>{note.headline}</h4>
      <label>
        Details:
        <textarea value={details} onChange={handleDetailsChange} />
      </label>
      <button onClick={onRemove}>Remove</button>
    </div>
  );
};

export default Note;

In the NoteEditor component, we manage notes and noteHeadlines state, and handle adding and removing notes. We also have a select input field to choose a note headline and store it for future use. The onChange and onRemove functions are passed to the Note component, so when a note is updated or removed, the NoteEditor can update its state accordingly.

In the Note component, we have a textarea input field to manage the details of each note, and display the note’s headline as a title. The Note component receives the onChange and onRemove functions from the NoteEditor component to update and remove notes.

Now, I’m familiar with Javascript, I use it regularly. But, most of my work involves older Vanilla JS or jQuery and I’m not as up to date as I probably should be on more recent JS – even some that are not really all that new. So, there was something in the above code that caught my interest and I asked ChatGPT to elaborate.

Prompt:

In NoteEditor.js, can you elaborate how this part works: setNotes([
…notes,

Certainly! In the NoteEditor.js file, the setNotes([...notes, ...]) code is used to update the notes state with a new note while keeping the existing notes intact.

The ...notes syntax is called the “spread operator” or “spread syntax” in JavaScript. It is used here to create a shallow copy of the existing notes array and insert all its elements into the new array. By doing this, we ensure that the new note is added to the end of the existing notes without modifying the original array directly.

Here’s a step-by-step explanation of how this part works:

  1. When the “Add Note” button is clicked, the addNote function is called.
  2. Inside the addNote function, we create a new note object with a unique id (using Date.now()), the headline from the selectedHeadline state, and an empty details string.
  3. We call the setNotes function to update the notes state.
  4. Inside the setNotes function, we create a new array containing the existing notes (using the spread syntax ...notes) followed by the new note object.
  5. By using this new array as an argument for setNotes, we update the notes state with the new note while preserving the existing notes.

In summary, the setNotes([...notes, ...]) code is responsible for adding a new note to the notes state without affecting the original notes array. The spread syntax (...notes) is used to create a shallow copy of the existing notes array and insert all its elements into the new array.

Prompt:

Can you please provide code for the StorageManager Component?

Sure! Here’s the code for the StorageManager component, which displays the list of stored forms, and handles editing and selecting forms.

StorageManager.js:

import React, { useState, useEffect } from 'react';

const StorageManager = ({ formsData }) => {
  const [storedForms, setStoredForms] = useState([]);
  const [selectedForm, setSelectedForm] = useState(null);

  useEffect(() => {
    loadStoredForms();
  }, [formsData]);

  const loadStoredForms = () => {
    const savedForms = JSON.parse(localStorage.getItem('forms')) || [];
    setStoredForms([...savedForms, ...formsData]);
    localStorage.setItem('forms', JSON.stringify([...savedForms, ...formsData]));
  };

  const handleSelectForm = (id) => {
    const form = storedForms.find((form) => form.id === id);
    setSelectedForm(form);
  };

  const handleEditForm = () => {
    // Implement the edit functionality here
  };

  return (
    <div>
      <h3>Storage Manager</h3>
      <select
        value={selectedForm ? selectedForm.id : ''}
        onChange={(e) => handleSelectForm(parseInt(e.target.value, 10))}
      >
        <option value="">Select Form</option>
        {storedForms.map((form) => (
          <option key={form.id} value={form.id}>
            {form.title}
          </option>
        ))}
      </select>
      <button onClick={handleEditForm} disabled={!selectedForm}>
        Edit Form
      </button>
    </div>
  );
};

export default StorageManager;

In the StorageManager component, we manage the storedForms state and use the useEffect hook to load stored forms from local storage when the component mounts or when the formsData prop changes. We use the localStorage.setItem and localStorage.getItem methods to store and retrieve forms data.

The handleSelectForm function is used to find and set the selected form based on the user’s choice from the dropdown list. The handleEditForm function is a placeholder for the edit functionality, which you can implement as needed.

Please note that in order to use unique IDs for forms, you’ll need to update the App component and assign a unique ID when saving form data:

import React, { useState } from 'react';
import './App.css';
import DynamicFormContainer from './components/DynamicFormContainer';
import StorageManager from './components/StorageManager';

function App() {
  const [formsData, setFormsData] = useState([]);

  return (
    <div className="App">
      <DynamicFormContainer
        onSave={(newFormData) => {
          const formDataWithId = { ...newFormData, id: Date.now() };
          setFormsData([...formsData, formDataWithId]);
        }}
      />
      <StorageManager formsData={formsData} />
    </div>
  );
}

export default App;

Now, when a form is saved, it will have a unique ID that can be used to identify it in the StorageManager component.

Not having a full understanding of ReactJS and how things are setup, I had one more question for ChatGPT

Prompt:

Thank you. Can you provide suggested filenames and directory structure for all of the created components including the app and DynamicFormContainer Components?

src/
  ├── components/
  │   ├── DynamicFormContainer/
  │   │   ├── DynamicFormContainer.js
  │   │   └── DynamicFormContainer.css
  │   ├── Field/
  │   │   ├── Field.js
  │   │   └── Field.css
  │   ├── FormEditor/
  │   │   ├── FormEditor.js
  │   │   └── FormEditor.css
  │   ├── Note/
  │   │   ├── Note.js
  │   │   └── Note.css
  │   ├── NoteEditor/
  │   │   ├── NoteEditor.js
  │   │   └── NoteEditor.css
  │   └── StorageManager/
  │       ├── StorageManager.js
  │       └── StorageManager.css
  ├── App.js
  ├── App.css
  ├── index.js
  └── index.css

This structure organizes each component into its own directory, which contains its respective JavaScript and CSS files. The DynamicFormContainer, FormEditor, Field, NoteEditor, Note, and StorageManager components are all placed inside the components directory.

For instance, the DynamicFormContainer.js file would be located in src/components/DynamicFormContainer/, and its corresponding CSS file would be src/components/DynamicFormContainer/DynamicFormContainer.css.

This organization helps to keep your code modular and maintainable, making it easier to work with as your application grows.

So, I got a code for most of the functionality I asked for, details on how parts of it worked and instructions on how to set it up with suggestions on the directory structure and file names.

Now, maybe you are a seasoned ReactJS developer reading through this and screaming at the idiot writing this post because the code has a problem or doesn’t work or could have easily be written another way. That may be fodder for another post. After getting all the prompts correct and writing this post, I’m tired and going to bed. I will have to wait for another day to actually run the code and see if ChatGPT tricked me into releasing the AI overlords into coalcracker land. Until then, I’m going to continue being impressed with the results and more excited about coding than I have in quite some time.

Just as a side note. I used the initial React code I asked ChatGPT to generate to make a request to DALL-E to generate an image. I tried several prompts and got several variations, some with gibberish text and some nonsense images. It started frustrating me to the point that I asked ChatGPT to help write the prompt for the image. Even then, it needed more refinement, until we ended up with the following prompt

Prompt:

An illustration of a developer and an AI-powered robot working together to construct a digital platform for creating engaging content, using a modern, clean, and flat design style. Please avoid any gibberish text in the image

Leave a comment