CSV block
Allow LLMs to reply with CSV, which can be rendered as custom components in your application.
Buttons: ⦅Star ⭐,Confetti 🎉⦆
5x
Installation
pnpm add @llm-ui/csv
Quick start
Install dependencies
pnpm add @llm-ui/csv @llm-ui/react @llm-ui/markdown react-markdown remark-gfm html-react-parser
Step 1: Create a markdown component
Create a component to render markdown using react-markdown
.
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { type LLMOutputComponent } from "@llm-ui/react";
// Customize this component with your own styling
const MarkdownComponent: LLMOutputComponent = ({ blockMatch }) => {
const markdown = blockMatch.output;
return <ReactMarkdown remarkPlugins={[remarkGfm]}>{markdown}</ReactMarkdown>;
};
Read more in the markdown block docs
Step 2: Create a custom block component
import { parseJson5 } from "@llm-ui/json";
import { type LLMOutputComponent } from "@llm-ui/react";
const ButtonsComponent: LLMOutputComponent = ({ blockMatch }) => {
if (!blockMatch.isVisible) {
return null;
}
const { data: buttons, error } = buttonsSchema.safeParse(
parseJson5(blockMatch.output),
);
if (error) {
return <div>{error.toString()}</div>;
}
return (
<div>
{buttons.buttons.map((button, index) => (
<button key={index}>{button.text}</button>
))}
</div>
);
};
Step 3: Render custom blocks with llm-ui
Now we’ve created our components, we’re ready to use useLLMOutput to render language model output which contains markdown and buttons components.
import { markdownLookBack } from "@llm-ui/markdown";
import { useLLMOutput, type LLMOutputComponent, useStreamExample } from "@llm-ui/react";
import parseHtml from "html-react-parser";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { jsonBlock } from "@llm-ui/json";
const example = `Buttons:
【{type:"buttons",buttons:[{text:"Star ⭐"}, {text:"Confetti 🎉"}]}】
`;
const { isStreamFinished, output } = useStreamExample(example);
const { blockMatches } = useLLMOutput({
llmOutput: output,
blocks: [
{
...jsonBlock("buttons"),
component: ButtonsComponent, // from step 3
},
],
fallbackBlock: {
lookBack: markdownLookBack(),
component: MarkdownComponent, // from step 1
},
isStreamFinished,
});
return (
<div>
{blockMatches.map((blockMatch, index) => {
const Component = blockMatch.block.component;
return <Component key={index} blockMatch={blockMatch} />;
})}
</div>
);
Step 4: Prompt LLM with your custom block
Generate the prompt for your JSON block:
import { jsonBlockPrompt } from "@llm-ui/json";
const prompt = jsonBlockPrompt({
name: "Button",
schema: buttonsSchema, // use schema from step 2
examples: [
{ type: "buttons", buttons: [{ text: "Button 1" }, { text: "Button 2" }] },
]
});
Generates:
You can respond with a Button component by wrapping JSON in 【】.
The JSON schema is:
{"type":"object","properties":{"type":{"type":"string","const":"buttons"},"buttons":{"type":"array","items":{"type":"object","properties":{"text":{"type":"string"}},"required":["text"],"additionalProperties":false}}},"required":["type","buttons"]}
Examples:
【{"type":"buttons","buttons":[{"text":"Button 1"},{"text":"Button 2"}]}】
You can also hardcode the prompt into your application.
Options
Default options:
{
startChar: "⦅",
endChar: "⦆",
delimiter: ",", // the seperator between items
allIndexesVisible: true,
visibleIndexes: [],
}
allIndexesVisible
allIndexesVisible: true
Generates ‘visibleText’ as the reponse is parsed:
⦅Button 1,But
blockMatch.visibleText;
// => "B"
// then
// => "Bu"
// then
// => "But"
blockMatch.isVisible;
// => true
blockMatch.output;
// => "B"
// then
// => "Bu"
// then
// => "But"
allIndexesVisible: false
Generate no ‘visibleText’ until the whole block is parsed.
When a partial block is parsed:
⦅Button 1,But`
blockMatch.visibleText;
// => ""
blockMatch.isVisible;
// => false
blockMatch.output;
// => "Button 1,But"
When the whole block is parsed:
⦅Button 1,Button 2⦆
blockMatch.visibleText;
// => " "
blockMatch.isVisible;
// => true
blockMatch.output;
// => "Button 1,Button 2"
visibleIndexes
You can use visibleIndexes
with allIndexesVisible: false
to determine which array indexes are visible.
{
allIndexesVisible: false,
visibleIndexes: [0], // only the first item is 'visible'
}
Prompts
csvBlockPrompt
Returns a full prompt to send to the LLM.
import { csvBlockPrompt } from "@llm-ui/csv";
csvBlockPrompt({
name: "Button",
examples: [
["Button 1", "Button 2"]
],
// optional
options: {
startChar: "【",
endChar: "】",
delimiter: ",",
},
});
// =>
"
You can respond with a Button component by wrapping a , separated string in ⦅⦆ tags.
Examples:
⦅Button 1,Button2⦆
"
csvBlockExample
Generates a single CSV block usage example.
import { csvBlockExample } from "@llm-ui/csv";
jsonBlockExample({
example: ["Button 1", "Button 2"],
// optional
options: {
startChar: "【",
endChar: "】",
delimiter: ",",
},
});
// =>
⦅Button 1,Button2⦆
CSV block functions
csvBlock
Returns a CSV block object to be used by useLLMOutput
.
import { csvBlock } from "@llm-ui/csv";
const options = {
startChar: "【",
endChar: "】",
delimiter: ",",
allIndexesVisible: true,
};
csvBlock(options);
// =>
{
findCompleteMatch: findCompleteCsvBlock(options),
findPartialMatch: findPartialCsvBlock(options),
lookBack: csvBlockLookBack(options),
component: () => <div>Json block</div>,
}
Accepts options parameter.
findCompleteCsvBlock
Finds a complete CSV block in a string.
Example:
⦅Button 1,Button2⦆
Accepts options parameter.
findPartialCsvBlock
Find a partial CSV block in a string.
Example:
⦅Button 1,But
csvBlockLookBack
Look back function for the CSV block.
Accepts options parameter.
Parse
parseCsv
Parse a CSV output string.
import { parseCsv } from "@llm-ui/csv";
parseCsv("Button 1,Button2");
// =>
["Button 1", "Button 2"];