DEV Community

Mingming Ma
Mingming Ma

Posted on

My PR to ChatCraft.org

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, 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, where he shares the inspiration behind his interest in developing ChatCraft.org. You won't want to miss it!

Issue

The issue was a feature request. When ChatCraft displayed the preview iframes, they all had the same height. issue Therefore, if the iframes can resize to fit their contents, it will make the platform more user-friendly. This issue also mentioned iframe-resizer and 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 and examples, I discovered two things:

  1. The Parent Page needs to have <IframeResizer> component.
  2. The IFramed Page nees the (iframeResizer.contentWindow.min.js) from iframe-resizer.

Let's look at the examples:
example/src/app.jsx

    <IframeResizer
          log
          inPageLinks
          forwardRef={ref}
          onMessage={onMessage}
          onResized={onResized}
          src="html/frame.content.html"
          width="100%"
          scrolling="no"
        />
Enter fullscreen mode Exit fullscreen mode

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

... 
    <script
      type="text/javascript"
      src="../js/iframeResizer.contentWindow.min.js"
      defer
    ></script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

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

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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:

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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 that demonstrates how to access static files:

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

document.getElementById('hero-img').src = imgUrl
Enter fullscreen mode Exit fullscreen mode

So here is the updated version of my PR

   const url = useMemo(() => {
    //...
    scriptElement.src = new URL("/js/iframeResizer.contentWindow.min.js", import.meta.url).href;
    //...;
  }, [children]);
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)