fix: enhanced HTML decoding and code fence stripping in AIAssist preview
This commit is contained in:
@@ -41,7 +41,16 @@ const LiveCanvas = memo(({ data, type, isStreaming }: { data: string, type: stri
|
|||||||
if (!iframeRef.current) return;
|
if (!iframeRef.current) return;
|
||||||
|
|
||||||
const isHtml = data.includes("<") && data.includes(">");
|
const isHtml = data.includes("<") && data.includes(">");
|
||||||
const shouldRender = isHtml || ["web", "app", "design", "html", "ui"].includes(type);
|
const isEncodedHtml = data.includes("<") && data.includes(">");
|
||||||
|
const shouldRender = isHtml || isEncodedHtml || ["web", "app", "design", "html", "ui"].includes(type);
|
||||||
|
const normalized = isEncodedHtml
|
||||||
|
? data
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/"/g, "\"")
|
||||||
|
.replace(/'/g, "'")
|
||||||
|
: data;
|
||||||
if (shouldRender) {
|
if (shouldRender) {
|
||||||
const doc = `
|
const doc = `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
@@ -79,7 +88,7 @@ const LiveCanvas = memo(({ data, type, isStreaming }: { data: string, type: stri
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-[#0b1414] text-emerald-50">
|
<body class="bg-[#0b1414] text-emerald-50">
|
||||||
${data}
|
${normalized}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`;
|
`;
|
||||||
@@ -118,6 +127,16 @@ function parseStreamingContent(text: string) {
|
|||||||
let agent = "general";
|
let agent = "general";
|
||||||
let preview: PreviewData | null = null;
|
let preview: PreviewData | null = null;
|
||||||
let chatDisplay = text.trim();
|
let chatDisplay = text.trim();
|
||||||
|
const decodeHtml = (value: string) => value
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/"/g, "\"")
|
||||||
|
.replace(/'/g, "'");
|
||||||
|
const stripFences = (value: string) => {
|
||||||
|
const fenced = value.match(/```(?:html|css|javascript|tsx|jsx|md|markdown)?\s*([\s\S]*?)```/i);
|
||||||
|
return fenced ? fenced[1].trim() : value.trim();
|
||||||
|
};
|
||||||
|
|
||||||
const jsonCandidate = text.trim();
|
const jsonCandidate = text.trim();
|
||||||
if (jsonCandidate.startsWith("{") && jsonCandidate.endsWith("}")) {
|
if (jsonCandidate.startsWith("{") && jsonCandidate.endsWith("}")) {
|
||||||
@@ -173,6 +192,13 @@ function parseStreamingContent(text: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (preview) {
|
||||||
|
const isHtmlLike = ["web", "app", "design", "html", "ui"].includes(preview.type) || preview.language === "html";
|
||||||
|
if (isHtmlLike) {
|
||||||
|
preview.data = decodeHtml(stripFences(preview.data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!chatDisplay && preview) {
|
if (!chatDisplay && preview) {
|
||||||
chatDisplay = `Rendering live artifact...`;
|
chatDisplay = `Rendering live artifact...`;
|
||||||
}
|
}
|
||||||
@@ -204,7 +230,10 @@ export default function AIAssist() {
|
|||||||
|
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
const canRenderPreview = previewData
|
const canRenderPreview = previewData
|
||||||
? ["web", "app", "design", "html", "ui"].includes(previewData.type) || previewData.data.includes("<")
|
? ["web", "app", "design", "html", "ui"].includes(previewData.type)
|
||||||
|
|| previewData.data.includes("<")
|
||||||
|
|| previewData.language === "html"
|
||||||
|
|| (previewData.data.includes("<") && previewData.data.includes(">"))
|
||||||
: false;
|
: false;
|
||||||
|
|
||||||
// Auto-scroll logic
|
// Auto-scroll logic
|
||||||
@@ -219,9 +248,9 @@ export default function AIAssist() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (previewData?.data) {
|
if (previewData?.data) {
|
||||||
setViewMode("preview");
|
setViewMode(canRenderPreview ? "preview" : "code");
|
||||||
}
|
}
|
||||||
}, [previewData?.data]);
|
}, [previewData?.data, canRenderPreview]);
|
||||||
|
|
||||||
// Load available models
|
// Load available models
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -478,7 +507,13 @@ export default function AIAssist() {
|
|||||||
size="sm"
|
size="sm"
|
||||||
className="mt-5 w-full bg-emerald-50 dark:bg-emerald-900/30 border border-emerald-200/60 dark:border-emerald-800 text-emerald-700 dark:text-emerald-200 font-black uppercase tracking-[0.1em] text-[10px] rounded-2xl h-11 hover:scale-[1.02] active:scale-[0.98] transition-all"
|
className="mt-5 w-full bg-emerald-50 dark:bg-emerald-900/30 border border-emerald-200/60 dark:border-emerald-800 text-emerald-700 dark:text-emerald-200 font-black uppercase tracking-[0.1em] text-[10px] rounded-2xl h-11 hover:scale-[1.02] active:scale-[0.98] transition-all"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPreviewData({ ...msg.preview!, isStreaming: false });
|
const nextPreview = { ...msg.preview!, isStreaming: false };
|
||||||
|
setPreviewData(nextPreview);
|
||||||
|
const nextCanRender = ["web", "app", "design", "html", "ui"].includes(nextPreview.type)
|
||||||
|
|| nextPreview.data.includes("<")
|
||||||
|
|| nextPreview.language === "html"
|
||||||
|
|| (nextPreview.data.includes("<") && nextPreview.data.includes(">"));
|
||||||
|
setViewMode(nextCanRender ? "preview" : "code");
|
||||||
setShowCanvas(true);
|
setShowCanvas(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user