My PR to ChatCraft.org
Published: 2023-10-31 19:56:03

[ChatCraft.org](https://github.com/tarasglek/chatcraft.org) is an awesome open-source web companion for coding with Large Language Models (LLMs). It provides essential ChatGPT features and offers users much more freedom, allowing them to customize all aspects of a chat and switch to a different model whenever they like. I learned about the project from my professor, [David Humphrey](https://github.com/humphd), and after using it, I grew to really like it. It not only offers me a cost-effective way to access GPT-4 but also allows me to develop a personalized chat UI tailored to my unique requirements. (Spoiler: I'm developing a tool focused on correcting my grammar issues.) Existing platforms never quite met my needs, but fortunately, I discovered ChatCraft.org. Take a moment to explore my professor's [blog](https://blog.humphd.org/chatcraft-org/), where he shares the inspiration behind his interest in developing ChatCraft.org. You won't want to miss it!

## Issue
The [issue](https://github.com/tarasglek/chatcraft.org/issues/205) was a feature request. When ChatCraft displayed the preview iframes, they all had the same height. ![issue](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ckn46le6qoxatu0r1u19.png) Therefore, if the iframes can resize to fit their contents, it will make the platform more user-friendly. This issue also mentioned [iframe-resizer](https://github.com/davidjbradshaw/iframe-resizer) and [iframe-resizer-react](https://github.com/davidjbradshaw/iframe-resizer-react) which provided a possible solution. That was really helpful for me to get started.

## Developing process

### Study proposal solution

The first step was studying the proposal solution iframe-resizer and iframe-resizer-react. Since ChatCraft uses React, I began with iframe-resizer-react. After reading the [document](https://github.com/davidjbradshaw/iframe-resizer-react#usage) and [examples](https://github.com/davidjbradshaw/iframe-resizer-react/tree/master/example), I discovered two things: 
1. The Parent Page needs to have `<IframeResizer>` component.
2. The IFramed Page nees the ([iframeResizer.contentWindow.min.js](https://raw.github.com/davidjbradshaw/iframe-resizer/master/js/iframeResizer.contentWindow.min.js)) from [iframe-resizer](https://github.com/davidjbradshaw/iframe-resizer).

Let's look at the examples:
[`example/src/app.jsx`](https://github.com/davidjbradshaw/iframe-resizer-react/blob/master/example/src/app.jsx)
```jsx
    <IframeResizer
          log
          inPageLinks
          forwardRef={ref}
          onMessage={onMessage}
          onResized={onResized}
          src="html/frame.content.html"
          width="100%"
          scrolling="no"
        />
```
In the `app.jsx` which usually serves as the entry point and typically defines the top-level component in a React demo, we can see it calles `IframeResizer` component. From the `src` we know the IFramed Page is `html/frame.content.html` which located in the Static Asset folder. 
Let's see [`html/frame.content.html`](https://github.com/davidjbradshaw/iframe-resizer-react/blob/master/example/public/html/frame.content.html) 

```html
... 
    <script
      type="text/javascript"
      src="../js/iframeResizer.contentWindow.min.js"
      defer
    ></script>
  </body>
</html>
```
We can find the IFramed page indeed has the `iframeResizer.contentWindow.min.js` which is as expected.

### First implementation
Next, I was going to find out how the preview is generated in ChatCraft. It is as straightforward as the name suggests: `/src/components/HtmlPreview.tsx`

```tsx
type HtmlPreviewProps = {
  children: ReactNode & ReactNode[];
};

const toUrl = (html: string) => URL.createObjectURL(new Blob([html], { type: "text/html" }));

const HtmlPreview = ({ children }: HtmlPreviewProps) => {
  const url = useMemo(() => toUrl(String(children)), [children]);

  return (
    <Card variant="outline" position="relative" mt={2} minHeight="12em" resize="vertical">
      <IconButton
        //...
        href={url}
        //...
      />
      <CardBody mt={6} p={2}>
        <chakra.iframe w="100%" minHeight="200px" frameBorder="0" src={url}></chakra.iframe>
      </CardBody>
    </Card>
  );
};
```

From `toUrl` we know it converted `children` which has the `text/html` contents to an URL object.

Becuase the `url` is the location of the iFramed page, I needed to modify it to include the `iframeResizer.contentWindow.min.js` as discussed above. Here is my initial [attempt](https://github.com/tarasglek/chatcraft.org/pull/267/commits/ba19e596231df3013bbc0ad49717dc944eb856ed):

```tsx
const url = useMemo(() => {
    const parser = new DOMParser();
    const doc = parser.parseFromString(String(children), "text/html");
    const scriptElement = document.createElement("script");
    scriptElement.src = "https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.7/iframeResizer.contentWindow.min.js";
    doc.body.appendChild(scriptElement);
    const HtmlContent = `<!DOCTYPE html>${doc.documentElement.innerHTML}`;
   return toUrl(HtmlContent);
  }, [children]);

  return (
    <Card variant="outline" position="relative" mt={2} minHeight="12em" resize="vertical">
      <IconButton
        //...
        href={url}
        //...
      />
      <CardBody mt={6} p={2}>
          <IframeResizer checkOrigin={false} src={url} width="100%" scrolling={true} />
      </CardBody>
    </Card>
  );
};
```
I parsed the content and add the `script` tag with a `CDN` `iframeResizer.contentWindow.min.js`.

### Optimize
After the initial version, I received feedback during the review process, and I came to the realization that using a CDN was not an ideal approach. What we can do is utilize Static Assets to optimize the code

Here is the document from [Vite](https://vitejs.dev/guide/assets.html#the-public-directory) that demonstrates how to access static files:

```tsx
const imgUrl = new URL('./img.png', import.meta.url).href

document.getElementById('hero-img').src = imgUrl
```

So here is the updated version of my PR
```tsx
   const url = useMemo(() => {
    //...
    scriptElement.src = new URL("/js/iframeResizer.contentWindow.min.js", import.meta.url).href;
    //...;
  }, [children]);
```

Then, I made further updates, including the use of the recommended width setup, removal of height capping, and the renaming of variables based on the feedback. For more details, you can refer to the discussion within the issues. Finally, I was thrilled to see this feature successfully deployed.

## Reflection

This contribution has been an invaluable learning experience for me and I am deeply appreciative of the opportunity provided by Professor Humphrey. This experience has reinforced my belief in the power of collaboration within this community, where we come together to learn and help one another. I look forward to more contributions in the open source world, all the while appreciating the ethos of sharing knowledge and working together.