Skip to content

Commit ec02757

Browse files
committed
Add an export feature along with the mermaid diagram. Add sidebar to loading page.
1 parent 889f34c commit ec02757

File tree

2 files changed

+107
-14
lines changed

2 files changed

+107
-14
lines changed

src/interface/web/app/components/loading/loading.tsx

+35-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1+
import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
12
import { CircleNotch } from "@phosphor-icons/react";
3+
import { AppSidebar } from "../appSidebar/appSidebar";
4+
import { Separator } from "@/components/ui/separator";
5+
import { useIsMobileWidth } from "@/app/common/utils";
6+
import { KhojLogoType } from "../logo/khojLogo";
27

38
interface LoadingProps {
49
className?: string;
@@ -7,21 +12,39 @@ interface LoadingProps {
712
}
813

914
export default function Loading(props: LoadingProps) {
15+
const isMobileWidth = useIsMobileWidth();
16+
1017
return (
1118
// NOTE: We can display usage tips here for casual learning moments.
12-
<div
13-
className={
14-
props.className ||
15-
"bg-background opacity-50 flex items-center justify-center h-screen"
16-
}
17-
>
18-
<div>
19-
{props.message || "Loading"}{" "}
20-
<span>
21-
<CircleNotch className="inline animate-spin h-5 w-5" />
22-
</span>
19+
<SidebarProvider>
20+
<AppSidebar conversationId={""} />
21+
<SidebarInset>
22+
<header className="flex h-16 shrink-0 items-center gap-2 border-b px-4">
23+
<SidebarTrigger className="-ml-1" />
24+
<Separator orientation="vertical" className="mr-2 h-4" />
25+
{isMobileWidth ? (
26+
<a className="p-0 no-underline" href="/">
27+
<KhojLogoType className="h-auto w-16" />
28+
</a>
29+
) : (
30+
<h2 className="text-lg">Ask Anything</h2>
31+
)}
32+
</header>
33+
</SidebarInset>
34+
<div
35+
className={
36+
props.className ||
37+
"bg-background opacity-50 flex items-center justify-center h-full w-full fixed top-0 left-0 z-50"
38+
}
39+
>
40+
<div>
41+
{props.message || "Loading"}{" "}
42+
<span>
43+
<CircleNotch className="inline animate-spin h-5 w-5" />
44+
</span>
45+
</div>
2346
</div>
24-
</div>
47+
</SidebarProvider>
2548
);
2649
}
2750

src/interface/web/app/components/mermaid/mermaid.tsx

+72-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { useEffect, useState, useRef } from "react";
22
import mermaid from "mermaid";
3-
import { Info } from "@phosphor-icons/react";
3+
import { Download, Info } from "@phosphor-icons/react";
4+
import { Button } from "@/components/ui/button";
45

56
interface MermaidProps {
67
chart: string;
@@ -41,8 +42,71 @@ const Mermaid: React.FC<MermaidProps> = ({ chart }) => {
4142
mermaid.contentLoaded();
4243
}, []);
4344

45+
const handleExport = async () => {
46+
if (!elementRef.current) return;
47+
48+
try {
49+
// Get SVG element
50+
const svgElement = elementRef.current.querySelector("svg");
51+
if (!svgElement) throw new Error("No SVG found");
52+
53+
// Get SVG viewBox dimensions
54+
const viewBox = svgElement.getAttribute("viewBox")?.split(" ").map(Number) || [
55+
0, 0, 0, 0,
56+
];
57+
const [, , viewBoxWidth, viewBoxHeight] = viewBox;
58+
59+
// Create canvas with viewBox dimensions
60+
const canvas = document.createElement("canvas");
61+
const scale = 2; // For better resolution
62+
canvas.width = viewBoxWidth * scale;
63+
canvas.height = viewBoxHeight * scale;
64+
const ctx = canvas.getContext("2d");
65+
if (!ctx) throw new Error("Failed to get canvas context");
66+
67+
// Convert SVG to data URL
68+
const svgData = new XMLSerializer().serializeToString(svgElement);
69+
const svgBlob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
70+
const svgUrl = URL.createObjectURL(svgBlob);
71+
72+
// Create and load image
73+
const img = new Image();
74+
img.src = svgUrl;
75+
76+
await new Promise((resolve, reject) => {
77+
img.onload = () => {
78+
// Scale context for better resolution
79+
ctx.scale(scale, scale);
80+
ctx.drawImage(img, 0, 0, viewBoxWidth, viewBoxHeight);
81+
82+
canvas.toBlob((blob) => {
83+
if (!blob) {
84+
reject(new Error("Failed to create blob"));
85+
return;
86+
}
87+
88+
const url = URL.createObjectURL(blob);
89+
const a = document.createElement("a");
90+
a.href = url;
91+
a.download = `mermaid-diagram-${Date.now()}.png`;
92+
a.click();
93+
94+
// Cleanup
95+
URL.revokeObjectURL(url);
96+
URL.revokeObjectURL(svgUrl);
97+
resolve(true);
98+
}, "image/png");
99+
};
100+
101+
img.onerror = () => reject(new Error("Failed to load SVG"));
102+
});
103+
} catch (error) {
104+
console.error("Error exporting diagram:", error);
105+
setMermaidError("Failed to export diagram");
106+
}
107+
};
108+
44109
useEffect(() => {
45-
console.log("Rendering mermaid chart:", chart);
46110
if (elementRef.current) {
47111
elementRef.current.removeAttribute("data-processed");
48112

@@ -79,6 +143,12 @@ const Mermaid: React.FC<MermaidProps> = ({ chart }) => {
79143
{chart}
80144
</div>
81145
)}
146+
{!mermaidError && (
147+
<Button onClick={handleExport} variant={"secondary"} className="mt-3">
148+
<Download className="w-5 h-5" />
149+
Export as PNG
150+
</Button>
151+
)}
82152
</div>
83153
);
84154
};

0 commit comments

Comments
 (0)