Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions apps/cockpit/src/lib/render-markdown.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ describe('renderMarkdown', () => {
expect(result.html).toContain('Build a streaming chat.');
});

it('parses inline markdown inside Summary blocks', async () => {
const md = '# Test\n\n<Summary>\nUse `agent()` from [`@ngaf/langgraph`](/docs/langgraph).\n</Summary>';
const result = await renderMarkdown(md);
expect(result.html).toContain('<code>agent()</code>');
expect(result.html).toContain('<a href="/docs/langgraph">');
});

it('renders Tip callout blocks', async () => {
const md = '# Test\n\n<Tip>\nNo service layer needed.\n</Tip>';
const result = await renderMarkdown(md);
Expand Down Expand Up @@ -89,6 +96,15 @@ describe('renderMarkdown', () => {
expect(result.html).toContain('data-copy-prompt');
});

it('renders Related blocks as markdown link lists', async () => {
const md = '# Test\n\n<Related>\n- [Chat Messages](/chat/core-capabilities/messages/overview/python) - Learn how messages render\n</Related>';
const result = await renderMarkdown(md);
expect(result.html).toContain('doc-related');
expect(result.html).toContain('<ul>');
expect(result.html).toContain('<a href="/chat/core-capabilities/messages/overview/python">Chat Messages</a>');
expect(result.html).not.toContain('- [Chat Messages]');
});

it('renders ApiTable blocks as styled tables', async () => {
const md = '# Test\n\n<ApiTable>\n| Signal | Type |\n|--------|------|\n| `messages()` | `BaseMessage[]` |\n</ApiTable>';
const result = await renderMarkdown(md);
Expand Down
52 changes: 34 additions & 18 deletions apps/cockpit/src/lib/render-markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface ExtractedBlock {
attrs: Record<string, string>;
}

const COMPONENT_TAGS = ['Summary', 'Tip', 'Note', 'Warning', 'Prompt', 'ApiTable', 'Step', 'Steps'];
const COMPONENT_TAGS = ['Summary', 'Tip', 'Note', 'Warning', 'Prompt', 'ApiTable', 'Related', 'Step', 'Steps'];

function extractComponentTags(source: string): { cleaned: string; blocks: ExtractedBlock[] } {
const blocks: ExtractedBlock[] = [];
Expand Down Expand Up @@ -41,14 +41,37 @@ function extractComponentTags(source: string): { cleaned: string; blocks: Extrac
return { cleaned, blocks };
}

function renderSummary(content: string): string {
return `<div class="doc-summary">${content}</div>`;
async function renderInlineMarkdown(content: string): Promise<string> {
return await marked.parseInline(content);
}

function renderCallout(type: 'tip' | 'note' | 'warning', content: string): string {
async function renderSummary(content: string): Promise<string> {
const html = await renderInlineMarkdown(content);
return `<div class="doc-summary">${html}</div>`;
}

async function renderCallout(
type: 'tip' | 'note' | 'warning',
content: string,
): Promise<string> {
const html = await renderInlineMarkdown(content);
const icons = { tip: '💡', note: '⚠️', warning: '🚨' };
const labels = { tip: 'Tip', note: 'Note', warning: 'Warning' };
return `<div class="doc-callout doc-callout--${type}"><div class="doc-callout__label">${icons[type]} ${labels[type]}</div><div class="doc-callout__content">${content}</div></div>`;
return `<div class="doc-callout doc-callout--${type}"><div class="doc-callout__label">${icons[type]} ${labels[type]}</div><div class="doc-callout__content">${html}</div></div>`;
}

async function renderPrompt(content: string): Promise<string> {
const html = await renderInlineMarkdown(content);
return `<div class="doc-prompt"><div class="doc-prompt__header"><span class="doc-prompt__label">🤖 Agentic Prompt</span><button class="doc-prompt__copy" data-copy-prompt>Copy prompt</button></div><div class="doc-prompt__content">${html}</div></div>`;
}

async function renderRelated(content: string): Promise<string> {
const html = await marked.parse(content);
return `<div class="doc-related">${html}</div>`;
}

function renderApiTable(content: string): string {
return `<div class="doc-api-table">${content}</div>`;
}

async function renderSteps(
Expand Down Expand Up @@ -100,14 +123,6 @@ async function parseStepContent(
return html;
}

function renderPrompt(content: string): string {
return `<div class="doc-prompt"><div class="doc-prompt__header"><span class="doc-prompt__label">🤖 Agentic Prompt</span><button class="doc-prompt__copy" data-copy-prompt>Copy prompt</button></div><div class="doc-prompt__content">${content}</div></div>`;
}

function renderApiTable(content: string): string {
return `<div class="doc-api-table">${content}</div>`;
}

function extractFilename(code: string): { filename: string | null; cleanedCode: string } {
const firstLine = code.split('\n')[0];
const tsMatch = firstLine?.match(/^\/\/\s*(.+\.\w+)\s*$/);
Expand Down Expand Up @@ -166,13 +181,14 @@ export async function renderMarkdown(source: string): Promise<RenderedMarkdown>
if (!html.includes(block.placeholder)) continue;
let rendered: string;
switch (block.type) {
case 'Summary': rendered = renderSummary(block.content); break;
case 'Tip': rendered = renderCallout('tip', block.content); break;
case 'Note': rendered = renderCallout('note', block.content); break;
case 'Warning': rendered = renderCallout('warning', block.content); break;
case 'Summary': rendered = await renderSummary(block.content); break;
case 'Tip': rendered = await renderCallout('tip', block.content); break;
case 'Note': rendered = await renderCallout('note', block.content); break;
case 'Warning': rendered = await renderCallout('warning', block.content); break;
case 'Steps': rendered = await renderSteps(block.content, blocks); break;
case 'Step': rendered = ''; break;
case 'Prompt': rendered = renderPrompt(block.content); break;
case 'Prompt': rendered = await renderPrompt(block.content); break;
case 'Related': rendered = await renderRelated(block.content); break;
case 'ApiTable': {
const tableHtml = await marked.parse(block.content);
rendered = renderApiTable(tableHtml);
Expand Down
18 changes: 18 additions & 0 deletions apps/cockpit/src/lib/route-resolution.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,24 @@ describe('getCapabilityPresentation', () => {
});
});

it('includes durable execution docs assets from the capability module', () => {
const entry = resolveCockpitEntry({
manifest: cockpitManifest,
product: 'langgraph',
section: 'core-capabilities',
topic: 'durable-execution',
page: 'overview',
language: 'python',
});
const presentation = getCapabilityPresentation(entry);

expect(presentation).toMatchObject({
kind: 'capability',
docsPath: '/docs/langgraph/core-capabilities/durable-execution/overview/python',
docsAssetPaths: ['cockpit/langgraph/durable-execution/python/docs/guide.md'],
});
});

it('presents render capabilities with module-backed metadata', () => {
const entry = resolveCockpitEntry({
manifest: cockpitManifest,
Expand Down
Loading
Loading