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 cli/templates/files/ai-agent/app/markdown-renderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"use client";

import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import type { MarkdownRendererProps } from "veryfront/markdown";

/**
* Rich Markdown for assistant answers.
*
* `veryfront/markdown` presents plain source until a renderer is installed, so
* this component supplies one. Swap in any renderer that accepts
* `MarkdownRendererProps` to change how answers are parsed and rendered.
*/
export function MarkdownRenderer({ source }: MarkdownRendererProps): React.JSX.Element {
return <ReactMarkdown remarkPlugins={[remarkGfm]}>{source}</ReactMarkdown>;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
8 changes: 7 additions & 1 deletion cli/templates/files/ai-agent/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
"use client";

import { Chat } from "veryfront/chat";
import { MarkdownRendererProvider } from "veryfront/markdown";
import { MarkdownRenderer } from "./markdown-renderer.tsx";

export default function ChatPage(): React.JSX.Element {
return <Chat agentId="assistant" className="h-screen" />;
return (
<MarkdownRendererProvider renderer={MarkdownRenderer}>
<Chat agentId="assistant" className="h-screen" />
</MarkdownRendererProvider>
);
}
15 changes: 12 additions & 3 deletions cli/templates/files/ai-agent/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,19 @@
"jsx": "react-jsx",
"skipLibCheck": true,
"esModuleInterop": true,
"noEmit": true,
"allowImportingTsExtensions": true,
"paths": {
"@/*": ["./*"]
"@/*": [
"./*"
]
}
},
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
"include": [
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"
]
}
16 changes: 16 additions & 0 deletions cli/templates/files/coding-agent/app/markdown-renderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use client'

import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import type { MarkdownRendererProps } from 'veryfront/markdown'

/**
* Rich Markdown for assistant answers.
*
* `veryfront/markdown` presents plain source until a renderer is installed, so
* this component supplies one. Swap in any renderer that accepts
* `MarkdownRendererProps` to change how answers are parsed and rendered.
*/
export function MarkdownRenderer({ source }: MarkdownRendererProps): React.JSX.Element {
return <ReactMarkdown remarkPlugins={[remarkGfm]}>{source}</ReactMarkdown>
}
16 changes: 10 additions & 6 deletions cli/templates/files/coding-agent/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
'use client'

import { Chat } from 'veryfront/chat'
import { MarkdownRendererProvider } from 'veryfront/markdown'
import { MarkdownRenderer } from './markdown-renderer.tsx'

export default function CodeAgent(): React.JSX.Element {
return (
<Chat
agentId="coder"
api="/api/ag-ui"
className="flex-1 min-h-0"
placeholder="Describe what you want to build or fix..."
/>
<MarkdownRendererProvider renderer={MarkdownRenderer}>
<Chat
agentId="coder"
api="/api/ag-ui"
className="flex-1 min-h-0"
placeholder="Describe what you want to build or fix..."
/>
</MarkdownRendererProvider>
)
}
15 changes: 12 additions & 3 deletions cli/templates/files/coding-agent/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,19 @@
"jsx": "react-jsx",
"skipLibCheck": true,
"esModuleInterop": true,
"noEmit": true,
"allowImportingTsExtensions": true,
"paths": {
"@/*": ["./*"]
"@/*": [
"./*"
]
}
},
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
"include": [
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"
]
}
16 changes: 16 additions & 0 deletions cli/templates/files/docs-agent/app/markdown-renderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use client'

import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import type { MarkdownRendererProps } from 'veryfront/markdown'

/**
* Rich Markdown for assistant answers.
*
* `veryfront/markdown` presents plain source until a renderer is installed, so
* this component supplies one. Swap in any renderer that accepts
* `MarkdownRendererProps` to change how answers are parsed and rendered.
*/
export function MarkdownRenderer({ source }: MarkdownRendererProps): React.JSX.Element {
return <ReactMarkdown remarkPlugins={[remarkGfm]}>{source}</ReactMarkdown>
}
18 changes: 11 additions & 7 deletions cli/templates/files/docs-agent/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
'use client'

import { Chat } from 'veryfront/chat'
import { MarkdownRendererProvider } from 'veryfront/markdown'
import { MarkdownRenderer } from './markdown-renderer.tsx'

export default function DocsChat() {
export default function DocsChat(): React.JSX.Element {
return (
<Chat
agentId="rag"
api="/api/ag-ui"
className="flex-1 min-h-0"
placeholder="Ask anything about your documents..."
/>
<MarkdownRendererProvider renderer={MarkdownRenderer}>
<Chat
agentId="rag"
api="/api/ag-ui"
className="flex-1 min-h-0"
placeholder="Ask anything about your documents..."
/>
</MarkdownRendererProvider>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use client'

import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import type { MarkdownRendererProps } from 'veryfront/markdown'

/**
* Rich Markdown for assistant answers.
*
* `veryfront/markdown` presents plain source until a renderer is installed, so
* this component supplies one. Swap in any renderer that accepts
* `MarkdownRendererProps` to change how answers are parsed and rendered.
*/
export function MarkdownRenderer({ source }: MarkdownRendererProps): React.JSX.Element {
return <ReactMarkdown remarkPlugins={[remarkGfm]}>{source}</ReactMarkdown>
}
16 changes: 10 additions & 6 deletions cli/templates/files/multi-agent-system/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
'use client'

import { Chat } from 'veryfront/chat'
import { MarkdownRendererProvider } from 'veryfront/markdown'
import { MarkdownRenderer } from './markdown-renderer.tsx'

export default function MultiAgentChat(): React.JSX.Element {
return (
<Chat
agentId="orchestrator"
api="/api/ag-ui"
className="flex-1 min-h-0"
placeholder="Give the team a task..."
/>
<MarkdownRendererProvider renderer={MarkdownRenderer}>
<Chat
agentId="orchestrator"
api="/api/ag-ui"
className="flex-1 min-h-0"
placeholder="Give the team a task..."
/>
</MarkdownRendererProvider>
)
}
15 changes: 12 additions & 3 deletions cli/templates/files/multi-agent-system/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,19 @@
"jsx": "react-jsx",
"skipLibCheck": true,
"esModuleInterop": true,
"noEmit": true,
"allowImportingTsExtensions": true,
"paths": {
"@/*": ["./*"]
"@/*": [
"./*"
]
}
},
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
"include": [
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"
]
}
65 changes: 64 additions & 1 deletion cli/templates/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
import { describe, it } from "#veryfront/testing/bdd.ts";
import type { EvalRecord } from "veryfront/eval";

import { getTemplate, templateConfigs } from "./index.ts";
import { getTemplate, getTemplateConfig, templateConfigs } from "./index.ts";
import { STARTER_TEMPLATE_NAMES, type TemplateName } from "./types.ts";

const STYLED_STARTER_TEMPLATES: TemplateName[] = [
Expand Down Expand Up @@ -844,3 +844,66 @@ describe("cli/templates", () => {
);
});
});

describe("chat starters scaffold a Markdown renderer", () => {
const CHAT_TEMPLATES: TemplateName[] = [
"ai-agent",
"coding-agent",
"docs-agent",
"multi-agent-system",
];

it("ships the renderer alongside the parser it imports", async () => {
for (const name of CHAT_TEMPLATES) {
const files = await getTemplate(name);
assertExists(files, `${name} should load`);

const renderer = files.find((file) => file.path === "app/markdown-renderer.tsx");
assertExists(renderer, `${name} should scaffold app/markdown-renderer.tsx`);

const dependencies = getTemplateConfig(name)?.npmDependencies ?? {};
for (const dependency of ["react-markdown", "remark-gfm"]) {
assertEquals(
typeof dependencies[dependency],
"string",
`${name} imports ${dependency} but does not install it`,
);
}
}
});

it("allows the relative .tsx import during consumer tsc", async () => {
for (const name of CHAT_TEMPLATES) {
const files = await getTemplate(name);
const tsconfig = files?.find((file) => file.path === "tsconfig.json");
assertExists(tsconfig, `${name} should declare consumer TypeScript options`);

// `app/page.tsx` imports `./markdown-renderer.tsx`, which consumer tsc
// rejects with TS5097 unless both options are set.
assertEquals(
tsconfig.content.includes('"allowImportingTsExtensions": true'),
true,
`${name} imports a .tsx module and must allow the extension`,
);
assertEquals(
tsconfig.content.includes('"noEmit": true'),
true,
`${name} needs noEmit to keep allowImportingTsExtensions valid`,
);
}
});

it("gives the router aliases the same dependencies as ai-agent", () => {
// `getTemplate` serves ai-agent files for these names, so the config has to
// follow or they scaffold the renderer with nothing to import.
const expected = getTemplateConfig("ai-agent")?.npmDependencies;

for (const alias of ["pages-router", "app-router"] as TemplateName[]) {
assertEquals(
getTemplateConfig(alias)?.npmDependencies,
expected,
`${alias} scaffolds the ai-agent files and must resolve its dependencies`,
);
}
});
});
40 changes: 37 additions & 3 deletions cli/templates/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,34 @@ export {
validateFeatures,
} from "./feature-loader.ts";

/**
* Markdown renderer dependencies for the chat starters.
*
* `veryfront/markdown` presents plain source until a renderer is installed, so
* every starter that renders `<Chat>` scaffolds one in `app/markdown-renderer.tsx`
* and installs the parser it uses. Versions are exact: these reach the browser
* through the module pipeline, where a floating range would resolve to whatever
* is latest at request time.
*/
const CHAT_MARKDOWN_DEPENDENCIES: Record<string, string> = {
"react-markdown": "9.0.3",
"remark-gfm": "4.0.1",
};

export const templateConfigs: Partial<Record<TemplateName, TemplateConfig>> = {
"ai-agent": {
npmDependencies: { ...CHAT_MARKDOWN_DEPENDENCIES },
},
"coding-agent": {
npmDependencies: { ...CHAT_MARKDOWN_DEPENDENCIES },
},
"multi-agent-system": {
npmDependencies: { ...CHAT_MARKDOWN_DEPENDENCIES },
},
"docs-agent": {
Comment thread
kojiwakayama marked this conversation as resolved.
firstPartyExtensions: ["@veryfront/ext-document-kreuzberg"],
npmDependencies: {
...CHAT_MARKDOWN_DEPENDENCIES,
"@kreuzberg/node": "^4.4.2",
"@kreuzberg/wasm": "4.5.2",
},
Expand All @@ -56,9 +80,19 @@ export const templateConfigs: Partial<Record<TemplateName, TemplateConfig>> = {

const DIRECTORY_BASED_TEMPLATES: TemplateName[] = [...STARTER_TEMPLATE_NAMES];

/**
* The router aliases scaffold the `ai-agent` files, so they must resolve the
* same config. Keeping the mapping in one place stops files and dependencies
* from drifting apart.
*/
function resolveTemplateAlias(name: TemplateName): TemplateName {
return name === "pages-router" || name === "app-router" ? "ai-agent" : name;
}

export async function getTemplate(name: TemplateName): Promise<TemplateFile[] | null> {
if (name === "pages-router" || name === "app-router") {
return getTemplate("ai-agent");
const resolved = resolveTemplateAlias(name);
if (resolved !== name) {
return getTemplate(resolved);
}

if (!DIRECTORY_BASED_TEMPLATES.includes(name)) {
Expand All @@ -78,7 +112,7 @@ export async function getTemplate(name: TemplateName): Promise<TemplateFile[] |
}

export function getTemplateConfig(name: TemplateName): TemplateConfig | null {
return templateConfigs[name] ?? null;
return templateConfigs[resolveTemplateAlias(name)] ?? null;
}

export function getAiRuleTemplate(templateName: string): string | null {
Expand Down
Loading