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. 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:
- The Parent Page needs to have
<IframeResizer>
component. - 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"
/>
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>
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>
);
};
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>
);
};
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
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]);
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)