i was using puck editor for web builder and has some components , i created a chatBot component and context file , chatbot will return a html code and i will set that value in context value but i wnatthat code into my editor as soon i got that value , for example AI code is in editor of builder
clientEditor.tsx
"use client";
import type { Data } from "@measured/puck";
import { DropZone, Puck } from "@measured/puck";
import config from "../../../puck.config";
import { useEffect, useState } from "react";
import ChatBox from "../../../components/ChatBox/ChatBox";
import { useAppContext } from "../../../contexts/HtmlContext";
export function Client({ path, data }: { path: string; data: Partial<Data> }) {
const [isOpen, setIsOpen] = useState(false);
const { html } = useAppContext();
function ToggleChatBox() {
setIsOpen((prev) => !prev);
}
const [puckData, setPuckData] = useState<Partial<Data>>({});
useEffect(() => {
if (html) {
const newData =
typeof html === "string" ? JSON.parse(html) : html;
setPuckData(newData);
}
}, [html]);
return (
<div>
{isOpen && (
<div className="chatbox-container">
<ChatBox />
</div>
)}
<button className="chatgpt-btn" onClick={ToggleChatBox}>
{isOpen ? (
<i className="bi bi-x-lg" style={{ fontSize: "24px" }}></i>
) : (
<i className="bi bi-chat-dots-fill" style={{ fontSize: "24px" }}></i>
)}
</button>
<Puck
config={config}
data={puckData}
onPublish={async (data) => {
await fetch("/puck/api", {
method: "post",
body: JSON.stringify({ data, path }),
});
}}
/>
</div>
);
}
chatBot.tsx
import React, { useRef } from "react";
import "bootstrap/dist/css/bootstrap.min.css";
import { useState } from "react";
import "./style.css";
import axios from "axios";
import { useAppContext } from "../../contexts/HtmlContext";
export default function ChatBox() {
const [chatHistory, setChatHistory] = useState<
{ role: "user" | "ai"; text: string }[]
>([]);
const userMsg = useRef<HTMLInputElement>(null);
const { setHtml } = useAppContext();
const [isReplied, setIsReplied] = useState(false);
async function SubmitUserMsg() {
if (!userMsg.current) {
return;
}
const msg = userMsg.current.value;
if (msg.trim().length <= 0) {
return;
}
setChatHistory((prev) => [
...prev,
{ role: "user", text: msg },
{ role: "ai", text: "Thinking..." },
]);
setIsReplied(false);
userMsg.current.value = "";
userMsg.current.focus();
try {
const res = await axios.post(`${process.env.NEXT_PUBLIC_URL}/api/chat`, {
messages: msg,
});
if (res.data.isResponse) {
if(res.data.isHtml){
const blockData = {
content: [
{
type: "RawHTMLBlock",
props: {
html: res.data.AIreply,
},
},
],
};
setHtml(blockData);
}
setChatHistory((prev) => {
const updated = [...prev];
updated[updated.length - 1] = { role: "ai", text: res.data.AIreply };
return updated;
});
setIsReplied(true);
}
} catch (e) {
setChatHistory((prev) => {
const updated = [...prev];
updated[updated.length - 1] = {
role: "ai",
text: "Error in AI response",
};
return updated;
});
}
}
function copyToClipboard(text: string) {
navigator.clipboard
.writeText(text)
.then(() => {
})
.catch((err) => {
console.error("Failed to copy: ", err);
});
}
return (
<>
<div id="webcrumbs" className="bg-white">
<div className="container-lg rounded-3 shadow-lg p-0 overflow-hidden bg-gradient">
<div className="bg-white bg-opacity-80 p-3 border-bottom">
<div className="d-flex justify-content-between align-items-center">
<div className="d-flex align-items-center">
<div className="bg-primary rounded-circle d-flex align-items-center justify-content-center me-3 icon-container">
<i className="bi bi-robot text-white"></i>
</div>
<div>
<h5 className="mb-0 fw-bold text-dark">AI Assistant</h5>
</div>
</div>
</div>
</div>
<div className="message-container p-3">
{chatHistory.map((item, ind) => {
if (item.role === "user") {
return (
<div className="d-flex mb-3 justify-content-end" key={ind}>
<div className="bg-white rounded-3 p-2 max-width-75 overflow-hidden">
<p className="mb-0 text-break">{item.text}</p>
</div>
<div className="bg-secondary rounded-circle d-flex align-items-center justify-content-center ms-3 icon-sm">
<i className="bi bi-person text-white"></i>
</div>
</div>
);
} else {
return (
<div className="d-flex mb-3" key={ind}>
<div className="bg-primary rounded-circle d-flex align-items-center justify-content-center me-3 icon-sm">
<i className="bi bi-robot text-white"></i>
</div>
<div className={isReplied ? 'ai-message-wrapper d-flex flex-column max-width-75' : 'd-flex flex-column max-width-75' }>
<div className="bg-white rounded-3 p-3 border shadow-sm w-100">
<p className="mb-0 text-dark">{item.text}</p>
</div>
<button className="copy-btn "
onClick={() => copyToClipboard(item.text)}
>
<i className="bi bi-clipboard"></i>
</button>
</div>
</div>
);
}
})}
</div>
<div className="bg-white bg-opacity-90 border-top p-3">
<div className="d-flex align-items-center">
<div className="position-relative flex-grow-1 me-2">
<input
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
SubmitUserMsg();
}
}}
ref={userMsg}
type="text"
placeholder="Type your message..."
className="form-control rounded-pill ps-3 pe-5 border-0 bg-light"
/>
<button
onClick={SubmitUserMsg}
className="btn btn-primary position-absolute end-0 top-50 translate-middle-y rounded-circle p-0 d-flex align-items-center justify-content-center send-btn"
>
<i className="bi bi-send text-white"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</>
);
}
puck.config.ts
import type { Config } from "@measured/puck";
import { DropZone } from "@measured/puck";
import Image from "next/image";
import React, { useState, useEffect } from "react";
import HTMLrender from "./components/HTMLrender";
type Props = {
RawHTMLBlock: {
html: string;
};
};
export const config: Config<Props> = {
components: {
RawHTMLBlock: {
render: HTMLrender,
fields: {
html: {
type: "textarea",
label: "HTML",
},
},
},
}
}
Htmlrenderer.tsx
export default function HTMLrender({ html}: { html: string }) {
console.log("HTMLrender component received html:", html);
return (
<div dangerouslySetInnerHTML={{ __html: html }} />
)
}```
in console log
{
"content": [
{
"type": "RawHTMLBlock",
"props": {
"html": "<button type="button" class="btn btn-primary">Click Me</button>"
}
}
]
}