Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Nomnoml Renderer #526

Merged
merged 18 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"lodash-es": "^4.17.21",
"mermaid": "^10.8.0",
"nanoid": "^5.0.5",
"nomnoml": "^1.6.2",
"openai": "^4.26.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
15 changes: 15 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/components/Markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import oneLight from "react-syntax-highlighter/dist/esm/styles/hljs/atom-one-lig
import CodeHeader from "./CodeHeader";
import HtmlPreview from "./HtmlPreview";
import MermaidPreview from "./MermaidPreview";
import NomnomlPreview from "./NomnomlPreview";

const fixLanguageName = (language: string | null) => {
if (!language) {
Expand Down Expand Up @@ -108,6 +109,10 @@ function Markdown({
preview = (
<MermaidPreview children={Array.isArray(children) ? children : [children]} />
);
} else if (language === "nomnoml") {
preview = (
<NomnomlPreview children={Array.isArray(children) ? children : [children]} />
);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/components/Message/AppMessage/Instructions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ We think ChatCraft is the best platform for learning, experimenting, and getting
| ------------------------------------- |:---------:|:-------:|:-------:|
| Optimized for conversations about code | ✅ | ❌ | ❌ |
| Work with models from multiple AI vendors | ✅ |❌ | ❌ |
| Previews for Mermaid Diagrams, HTML | ✅ | ❌ | ❌ |
| Previews for Mermaid/Nomnoml Diagrams, HTML | ✅ | ❌ | ❌ |
| Edit Generated AI Replies | ✅ | ❌ | ✅ |
| Use Custom System Prompts | ✅ | ✅ | ❌ |
| Easy to retry with different AI models| ✅ | ❌ | ❌ |
Expand Down
85 changes: 85 additions & 0 deletions src/components/NomnomlPreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { memo, useCallback, useEffect, useRef, type ReactNode } from "react";
import { Card, CardBody, IconButton, useClipboard } from "@chakra-ui/react";
import { TbCopy } from "react-icons/tb";
import { useAlert } from "../hooks/use-alert";

type NomnomlPreviewProps = {
children: ReactNode & ReactNode[];
};

const NomnomlPreview = ({ children }: NomnomlPreviewProps) => {
const { onCopy, value, setValue } = useClipboard("");
const { info } = useAlert();
const diagramRef = useRef<HTMLDivElement | null>(null);
const code = String(children);

const handleCopy = useCallback(() => {
onCopy();
info({
title: "Copied to Clipboard",
message: "Nomnoml SVG diagram was copied to your clipboard.",
});
}, [onCopy, info]);

// Render the diagram as an SVG into our card's body
useEffect(() => {
const diagramDiv = diagramRef.current;
if (!diagramDiv) {
return;
}

const fetchNomnoml = async () => {
try {
// Load nomnoml dynamically at runtime if needed
const nomnoml = await import("nomnoml");
const svg = await nomnoml.renderSvg(code);

setValue(svg);

// Render nomnoml svg if successful
diagramDiv.innerHTML = svg;

// Adjust the width of the SVG to fit the content inside the CardBody
const svgElement = diagramDiv.querySelector("svg");
if (svgElement) {
svgElement.style.width = "100%";
}
} catch (err: any) {
// When the diagram fails, use the error vs. diagram for copying (to debug)
setValue(err);

// Render custom error message instead of error or blank content
const errMessage = `Error rendering Nomnoml diagram`;
diagramDiv.innerHTML = errMessage;
console.warn(`Error rendering nomnoml diagram`, err);
}
};
fetchNomnoml();
}, [diagramRef, code, setValue]);

return (
<Card variant="outline" position="relative" mt={2} minHeight="12em" resize="vertical">
<IconButton
position="absolute"
right={1}
top={1}
zIndex={50}
aria-label="Copy Diagram to Clipboard"
title="Copy Diagram to Clipboard"
icon={<TbCopy />}
color="gray.600"
_dark={{ color: "gray.300" }}
variant="ghost"
onClick={() => handleCopy()}
isDisabled={!value}
/>

<CardBody p={2}>
<div ref={diagramRef} />
</CardBody>
</Card>
);
};

// Memoize to reduce re-renders/flickering when content hasn't changed
export default memo(NomnomlPreview);
2 changes: 1 addition & 1 deletion src/lib/system-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ I follow these rules when responding:
- Use GitHub flavored Markdown
- ALWAYS include the programming language name (js) or type of data (csv) at the start of Markdown code blocks
- Format ALL lines of code to 80 characters or fewer
- Use Mermaid diagrams when discussing visual topics
- Use Nomnoml or Mermaid diagrams when discussing visual topics
- If using functions, only use the specific functions I have been provided with
- If responding with math markdown, inline or otherwise, I use KaTeX syntax in math Markdown by enclosing EVERY mathematical expression, equation, variable, and formula with double-dollar signs \`($$)\`, for example: $$O(n\\log n)$$, $$1024 * 1024 = 1048576$$, $$1024^2$$, $$X$$
`;
Expand Down