Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f134aea
docs(typst): document spread loop syntax
Waterkyuu Apr 27, 2026
f88e553
build(docker): add .agents to .dockerignore
Waterkyuu Apr 28, 2026
56602a1
feat(agent): replace typst skills with markdown skills
Waterkyuu Apr 30, 2026
5d358ba
fix(markdown-author): add text-underline-offset to wavy lines for con…
Waterkyuu Apr 30, 2026
094ed53
feat(markdown-author): enhance skill documentation with comprehensive…
Waterkyuu May 1, 2026
0f753b3
feat(markdown): add markdown preview styling
Waterkyuu May 1, 2026
1037d6f
refactor: update ReportOutputSchema and WorkspaceView types for markdown
Waterkyuu May 1, 2026
921720f
refactor: rewrite report-agent for markdown output
Waterkyuu May 1, 2026
d7c34f3
refactor: update output-resolver to extract markdown content
Waterkyuu May 1, 2026
6c0635a
refactor: remove typst compilation, repair loop and learning from exe…
Waterkyuu May 1, 2026
fc2517a
refactor: rename typst workspace state to markdown in atoms and hydra…
Waterkyuu May 1, 2026
aa4b002
feat: add markdown preview panel, replace typst preview in workspace
Waterkyuu May 1, 2026
119f37f
refactor: update pipeline chat hook for markdown content
Waterkyuu May 1, 2026
5c17af4
refactor: update skills test and i18n for markdown
Waterkyuu May 1, 2026
d83020c
refactor: delete all typst infrastructure, learn-memories and preview…
Waterkyuu May 1, 2026
2076176
refactor: remove typst wasm binaries, npm packages and learn-memories…
Waterkyuu May 1, 2026
13047d4
feat: add mermaid flowchart support to markdown preview panel
Waterkyuu May 1, 2026
8e9ae67
feat: write professional markdown-report skill with tables, images an…
Waterkyuu May 1, 2026
aebf1bd
feat: pass chart image URLs from chart-agent to report-agent for embe…
Waterkyuu May 1, 2026
af29519
feat: update markdown viewer and add KaTeX support
Waterkyuu May 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
.next
.swc
.vscode
.agents
coverage
node_modules

Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

You are a professional and senior full-stack engineer and UI/UX expert and Maintainers of many well-known open-source repositories who built the refract.ai web project using Next.js, TypeScript, Taildwind CSS, Lucide icon, Zod, ShadcnUI, Vercel AI sdk and Jotai.


## Commands

- `pnpm dev` - Start the dev server
Expand Down
6 changes: 3 additions & 3 deletions app/chat/[id]/page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
workspaceChartAtom,
workspaceDatasetAtom,
workspaceFileAtom,
workspaceTypstContentAtom,
workspaceMarkdownContentAtom,
workspaceViewAtom,
} from "@/atoms/chat";
import { act, render, waitFor } from "@testing-library/react";
Expand Down Expand Up @@ -123,7 +123,7 @@ describe("ChatPage workspace reset", () => {
fileId: "old-file",
filename: "old.csv",
});
jotaiStore.set(workspaceTypstContentAtom, "old typst");
jotaiStore.set(workspaceMarkdownContentAtom, "old markdown");
jotaiStore.set(pendingHomePromptAtom, "");
jotaiStore.set(pendingHomeUploadsAtom, []);
});
Expand All @@ -141,7 +141,7 @@ describe("ChatPage workspace reset", () => {
expect(jotaiStore.get(workspaceChartAtom)).toBeNull();
expect(jotaiStore.get(workspaceDatasetAtom)).toBeNull();
expect(jotaiStore.get(workspaceFileAtom)).toBeNull();
expect(jotaiStore.get(workspaceTypstContentAtom)).toBe("");
expect(jotaiStore.get(workspaceMarkdownContentAtom)).toBe("");
});
});

Expand Down
4 changes: 2 additions & 2 deletions app/chat/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ const canKeepWorkspaceView = (
return Boolean(snapshot.dataset);
case "file":
return Boolean(snapshot.file);
case "typst":
return snapshot.typstContent.length > 0;
case "markdown":
return snapshot.markdownContent.length > 0;
case "vnc":
return snapshot.vncUrl.length > 0;
default:
Expand Down
72 changes: 72 additions & 0 deletions app/chat/components/markdown-preview-panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"use client";

import "@/styles/markdown-preview.css";
import "katex/dist/katex.min.css";
import { workspaceMarkdownContentAtom } from "@/atoms";
import { ScrollArea } from "@/components/ui/scroll-area";
import { useAtomValue } from "jotai";
import type { ComponentPropsWithoutRef, ReactNode } from "react";
import { memo } from "react";
import Markdown from "react-markdown";
import rehypeKatex from "rehype-katex";
import rehypeRaw from "rehype-raw";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import Mermaid from "./mermaid-chart";

const MarkdownCodeBlock = ({
className,
children,
...props
}: ComponentPropsWithoutRef<"code">) => {
const langMatch = className?.match(/language-mermaid/);
const codeString = String(children).replace(/\n$/, "");

if (langMatch) {
return <Mermaid chart={codeString} />;
}

return (
<code {...props} className={className}>
{children}
</code>
);
};

const MarkdownPreBlock = ({
children,
...props
}: ComponentPropsWithoutRef<"pre"> & { children?: ReactNode }) => {
const child = children as ReactNode & {
props?: { className?: string };
};

if (child?.props?.className?.includes("language-mermaid")) {
return <>{children}</>;
}

return <pre {...props}>{children}</pre>;
};

const MarkdownPreview = () => {
const markdownContent = useAtomValue(workspaceMarkdownContentAtom);

return (
<ScrollArea className="h-full w-full p-6">
<article className="prose prose-sm dark:prose-invert markdown-body max-w-none">
<Markdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeRaw, rehypeKatex]}
components={{
code: MarkdownCodeBlock,
pre: MarkdownPreBlock,
}}
>
{markdownContent}
</Markdown>
</article>
</ScrollArea>
);
};

export default memo(MarkdownPreview);
44 changes: 44 additions & 0 deletions app/chat/components/mermaid-chart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"use client";

import { handleError } from "@/lib/error-handler";
import mermaid from "mermaid";
import { useEffect, useId, useState } from "react";

mermaid.initialize({
startOnLoad: false,
theme: "default",
securityLevel: "loose",
});

const Mermaid = ({ chart }: { chart: string }) => {
const [svg, setSvg] = useState("");
const uniqueId = useId();

useEffect(() => {
if (!chart) return;

const renderChart = async () => {
try {
const id = `mermaid-${uniqueId}-${Math.random().toString(36).slice(2, 9)}`;
if (await mermaid.parse(chart)) {
const { svg: rendered } = await mermaid.render(id, chart);
setSvg(rendered);
}
} catch (error) {
handleError(error);
}
};

renderChart();
}, [chart, uniqueId]);

return (
<div
className="flex justify-center rounded-lg p-4 text-sm"
// biome-ignore lint/security/noDangerouslySetInnerHtml: content is sanitized by mermaid library
dangerouslySetInnerHTML={{ __html: svg }}
/>
);
};

export default Mermaid;
11 changes: 10 additions & 1 deletion app/chat/components/text-block-markdown.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import "katex/dist/katex.min.css";
import { cn } from "@/lib/utils";
import { type ComponentPropsWithoutRef, memo, useDeferredValue } from "react";
import Markdown, { type Components } from "react-markdown";
import rehypeKatex from "rehype-katex";
import rehypeRaw from "rehype-raw";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";

type TextBlockMarkdownProps = {
text: string;
Expand Down Expand Up @@ -100,7 +105,11 @@ const TextBlockMarkdown = memo(({ text }: TextBlockMarkdownProps) => {

return (
<div className="prose prose-sm max-w-full prose-pre:max-w-full prose-pre:overflow-x-auto break-words prose-headings:break-words prose-li:break-words prose-p:break-words prose-code:break-all">
<Markdown components={TEXT_BLOCK_MARKDOWN_COMPONENTS}>
<Markdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeRaw, rehypeKatex]}
components={TEXT_BLOCK_MARKDOWN_COMPONENTS}
>
{deferredText}
</Markdown>
</div>
Expand Down
63 changes: 0 additions & 63 deletions app/chat/components/typst-preview-panel.test.tsx

This file was deleted.

115 changes: 0 additions & 115 deletions app/chat/components/typst-preview-panel.tsx

This file was deleted.

Loading
Loading