diff --git a/app/chat/[id]/page.tsx b/app/chat/[id]/page.tsx
index 62833ac..8a5c069 100644
--- a/app/chat/[id]/page.tsx
+++ b/app/chat/[id]/page.tsx
@@ -11,6 +11,7 @@ import {
showChartWorkspaceAtom,
showDatasetWorkspaceAtom,
showFileWorkspaceAtom,
+ showMarkdownWorkspaceAtom,
vncUrlAtom,
workspaceChartAtom,
workspaceDatasetAtom,
@@ -346,6 +347,8 @@ const ChatPage = ({ params }: ChatPageProps) => {
filename: artifact.filename,
});
}
+ } else if (artifact.markdownContent) {
+ jotaiStore.set(showMarkdownWorkspaceAtom, artifact.markdownContent);
} else if (artifact.fileId && artifact.filename) {
jotaiStore.set(showFileWorkspaceAtom, {
downloadUrl: getFileDownloadUrl(
diff --git a/app/chat/components/assistant-message-round-artifacts.test.tsx b/app/chat/components/assistant-message-round-artifacts.test.tsx
index 082c4f0..5770689 100644
--- a/app/chat/components/assistant-message-round-artifacts.test.tsx
+++ b/app/chat/components/assistant-message-round-artifacts.test.tsx
@@ -79,6 +79,21 @@ const assistantMessageWithMultipleCharts = {
],
} as unknown as UIMessage;
+const assistantMessageWithInlineReport = {
+ id: "assistant-round-inline-report",
+ role: "assistant",
+ parts: [
+ {
+ type: "text",
+ text: "Report completed",
+ },
+ {
+ type: "markdown-content",
+ content: "# Analysis Report\n\nRevenue increased by 12%.",
+ },
+ ],
+} as unknown as UIMessage;
+
describe("AssistantMessage round artifact actions", () => {
it("renders per-category buttons with icon prefix and opens the selected artifact", () => {
const onSelectRoundArtifact = jest.fn();
@@ -147,4 +162,27 @@ describe("AssistantMessage round artifact actions", () => {
expect(screen.getByText("CSV 312 Bytes")).toBeInTheDocument();
});
+
+ it("renders inline markdown report as a round artifact action", () => {
+ const onSelectRoundArtifact = jest.fn();
+
+ render(
+ ,
+ );
+
+ fireEvent.click(screen.getByRole("button", { name: /Report/i }));
+
+ expect(onSelectRoundArtifact).toHaveBeenCalledWith(
+ expect.objectContaining>({
+ category: "report",
+ label: "Analysis Report",
+ markdownContent: "# Analysis Report\n\nRevenue increased by 12%.",
+ }),
+ );
+ });
});
diff --git a/lib/chat/workspace-hydration.test.ts b/lib/chat/workspace-hydration.test.ts
index 6647236..2bfa6e9 100644
--- a/lib/chat/workspace-hydration.test.ts
+++ b/lib/chat/workspace-hydration.test.ts
@@ -325,4 +325,28 @@ describe("workspace hydration", () => {
);
expect(snapshot.view).toBe("dataset");
});
+
+ it("derives report artifacts from inline markdown content", () => {
+ const assistantMessage: UIMessage = {
+ id: "assistant-inline-report",
+ role: "assistant",
+ parts: [
+ {
+ type: "markdown-content",
+ content: "# Analysis Report\n\nRevenue increased by 12%.",
+ } as unknown as UIMessage["parts"][number],
+ ],
+ };
+
+ const artifacts = deriveRoundArtifactsFromMessage(assistantMessage);
+
+ expect(artifacts.report).toHaveLength(1);
+ expect(artifacts.report[0]).toEqual(
+ expect.objectContaining({
+ category: "report",
+ label: "Analysis Report",
+ markdownContent: "# Analysis Report\n\nRevenue increased by 12%.",
+ }),
+ );
+ });
});
diff --git a/lib/chat/workspace-hydration.ts b/lib/chat/workspace-hydration.ts
index 4663e4a..70e8fcc 100644
--- a/lib/chat/workspace-hydration.ts
+++ b/lib/chat/workspace-hydration.ts
@@ -20,6 +20,7 @@ type WorkspaceRoundArtifact = {
downloadUrl?: string;
extension?: string;
chart?: Record;
+ markdownContent?: string;
title?: string;
toolCallId?: string;
};
@@ -100,6 +101,16 @@ const getFileDownloadUrl = (
return undefined;
};
+const getMarkdownReportLabel = (content: string): string => {
+ const heading = content
+ .split("\n")
+ .map((line) => line.trim())
+ .find((line) => line.startsWith("#"));
+ const normalizedHeading = heading?.replace(/^#+\s*/, "").trim();
+
+ return normalizedHeading || "Generated report";
+};
+
// Resolve dataset attachments from user metadata even when preview is omitted.
const resolveDatasetAttachment = (
attachments: ChatAttachment[] | undefined,
@@ -284,6 +295,25 @@ const deriveRoundArtifactsFromMessage = (
continue;
}
+ if (
+ partRecord.type === "markdown-content" &&
+ typeof partRecord.content === "string" &&
+ partRecord.content.trim().length > 0
+ ) {
+ const createdAt = nextCreatedAt();
+ const markdownContent = partRecord.content.trim();
+ const label = getMarkdownReportLabel(markdownContent);
+ pushRoundArtifact(artifacts, {
+ id: `${message.id}-report-markdown-${createdAt}`,
+ category: "report",
+ createdAt,
+ label,
+ markdownContent,
+ title: label,
+ });
+ continue;
+ }
+
if (
typeof partRecord.type !== "string" ||
!partRecord.type.startsWith("tool-")
@@ -539,6 +569,11 @@ const deriveWorkspaceSnapshotFromMessages = (
}
if (artifact.category === "report") {
+ if (artifact.markdownContent) {
+ snapshot.markdownContent = artifact.markdownContent;
+ markViewUpdated("markdown");
+ continue;
+ }
if (!artifact.fileId || !artifact.filename) {
continue;
}