diff --git a/.npmrc b/.npmrc
new file mode 100644
index 00000000..5dbd1869
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,2 @@
+# this is needed to stop pnpm defaulting to workspace:* for the packages for some reason
+link-workspace-packages=false
diff --git a/apps/www/package.json b/apps/www/package.json
index 5f1e5ef4..580d522e 100644
--- a/apps/www/package.json
+++ b/apps/www/package.json
@@ -27,10 +27,11 @@
"@iconify-json/mdi": "^1.1.64",
"@iconify-json/ri": "^1.1.20",
"@llm-ui/code": "workspace:*",
+ "@llm-ui/csv": "workspace:*",
"@llm-ui/examples": "workspace:*",
+ "@llm-ui/json": "workspace:*",
"@llm-ui/markdown": "workspace:*",
"@llm-ui/react": "workspace:*",
- "@llm-ui/json": "workspace:*",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-avatar": "^1.0.4",
@@ -73,6 +74,7 @@
"tailwind-merge": "^2.2.1",
"tailwindcss": "^3.4.1",
"tailwindcss-animate": "^1.0.7",
+ "@prettier/sync": "^0.5.2",
"zod": "^3.23.8"
},
"devDependencies": {
diff --git a/apps/www/src/animations/buttonHandler.ts b/apps/www/src/animations/buttonHandler.ts
new file mode 100644
index 00000000..98671573
--- /dev/null
+++ b/apps/www/src/animations/buttonHandler.ts
@@ -0,0 +1,10 @@
+import { fireConfetti } from "./confetti";
+import { multipleStars } from "./stars";
+
+export const starsAndConfetti = (text: string = "") => {
+ if (text.toLowerCase().includes("star")) {
+ multipleStars();
+ } else if (text.toLowerCase().includes("confetti")) {
+ fireConfetti();
+ }
+};
diff --git a/apps/www/src/components/demo/message/Message.tsx b/apps/www/src/components/demo/message/Message.tsx
index bf0c8f1f..8429c96e 100644
--- a/apps/www/src/components/demo/message/Message.tsx
+++ b/apps/www/src/components/demo/message/Message.tsx
@@ -1,11 +1,13 @@
"use client";
-import { buttonsBlock as createButtonsBlock } from "@/components/examples/Buttons";
+import { buttonsCsvBlock as createCsvButtonsBlock } from "@/components/examples/ButtonsCsv";
+import { buttonsJsonBlock as createJsonButtonsBlock } from "@/components/examples/ButtonsJson";
import { codeBlockBlock } from "@/components/examples/CodeBlock";
import { Markdown } from "@/components/examples/Markdown";
import { markdownLookBack } from "@llm-ui/markdown";
import { useLLMOutput } from "@llm-ui/react";
-const buttonsBlock = createButtonsBlock();
+const buttonsJsonBlock = createJsonButtonsBlock();
+const buttonsCsvBlock = createCsvButtonsBlock();
export const Message: React.FC<{
message: string;
@@ -17,7 +19,7 @@ export const Message: React.FC<{
component: Markdown,
lookBack: markdownLookBack(),
},
- blocks: [codeBlockBlock, buttonsBlock],
+ blocks: [codeBlockBlock, buttonsJsonBlock, buttonsCsvBlock],
isStreamFinished,
});
diff --git a/apps/www/src/components/examples/ButtonsCsv.tsx b/apps/www/src/components/examples/ButtonsCsv.tsx
new file mode 100644
index 00000000..ee3a4b58
--- /dev/null
+++ b/apps/www/src/components/examples/ButtonsCsv.tsx
@@ -0,0 +1,38 @@
+import { starsAndConfetti } from "@/animations/buttonHandler";
+import { csvBlock, parseCsv } from "@llm-ui/csv";
+import type { LLMOutputBlock, LLMOutputComponent } from "@llm-ui/react";
+import { Button } from "../ui/Button";
+
+type OnClick = (buttonText: string | undefined) => void;
+
+const options = {
+ type: "buttons",
+ delimiter: ";",
+};
+
+const buttonsComponent = (onClick: OnClick) => {
+ const ButtonsComponent: LLMOutputComponent = ({ blockMatch }) => {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const [_type, ...buttons] = parseCsv(blockMatch.output, options);
+ if (!buttons || !blockMatch.isVisible) {
+ return undefined;
+ }
+ return (
+
+ {buttons?.map((buttonText, index) => (
+
+ ))}
+
+ );
+ };
+ return ButtonsComponent;
+};
+
+export const buttonsCsvBlock = (
+ onClick: OnClick = starsAndConfetti,
+): LLMOutputBlock => ({
+ ...csvBlock(options),
+ component: buttonsComponent(onClick),
+});
diff --git a/apps/www/src/components/examples/Buttons.tsx b/apps/www/src/components/examples/ButtonsJson.tsx
similarity index 76%
rename from apps/www/src/components/examples/Buttons.tsx
rename to apps/www/src/components/examples/ButtonsJson.tsx
index cf9d22bb..0e4651cd 100644
--- a/apps/www/src/components/examples/Buttons.tsx
+++ b/apps/www/src/components/examples/ButtonsJson.tsx
@@ -1,5 +1,4 @@
-import { fireConfetti } from "@/animations/confetti";
-import { multipleStars } from "@/animations/stars";
+import { starsAndConfetti } from "@/animations/buttonHandler";
import { jsonBlock, parseJson5 } from "@llm-ui/json";
import type { LLMOutputBlock, LLMOutputComponent } from "@llm-ui/react";
import z from "zod";
@@ -34,15 +33,7 @@ const buttonsComponent = (onClick: OnClick) => {
return ButtonsComponent;
};
-export const starsAndConfetti = (buttonText: string = "") => {
- if (buttonText.toLowerCase().includes("star")) {
- multipleStars();
- } else if (buttonText.toLowerCase().includes("confetti")) {
- fireConfetti();
- }
-};
-
-export const buttonsBlock = (
+export const buttonsJsonBlock = (
onClick: OnClick = starsAndConfetti,
): LLMOutputBlock => ({
...jsonBlock({ type: "buttons", defaultVisible: true }),
diff --git a/apps/www/src/components/examples/Example.tsx b/apps/www/src/components/examples/Example.tsx
index a8e49d79..c398a85a 100644
--- a/apps/www/src/components/examples/Example.tsx
+++ b/apps/www/src/components/examples/Example.tsx
@@ -1,3 +1,4 @@
+import { starsAndConfetti } from "@/animations/buttonHandler";
import { cn, delay } from "@/lib/utils";
import { markdownLookBack } from "@llm-ui/markdown";
import {
@@ -20,7 +21,8 @@ import React, {
} from "react";
import { Loader } from "../ui/custom/Loader";
import { H2 } from "../ui/custom/Text";
-import { buttonsBlock, starsAndConfetti } from "./Buttons";
+import { buttonsCsvBlock } from "./ButtonsCsv";
+import { buttonsJsonBlock } from "./ButtonsJson";
import { codeBlockBlock } from "./CodeBlock";
import { Controls } from "./Controls";
import { Markdown } from "./Markdown";
@@ -209,7 +211,9 @@ export const ExampleTabsTokenArray: React.FC = ({
starsAndConfetti(buttonText);
}
}, []);
- const buttonsBlockRef = useRef(buttonsBlock(onButtonClick));
+ const buttonsBlockRef = useRef(
+ buttonsJsonBlock(onButtonClick),
+ );
const {
output,
@@ -225,7 +229,7 @@ export const ExampleTabsTokenArray: React.FC = ({
const { finishCount, restart, blockMatches, isFinished, visibleText } =
useLLMOutput({
llmOutput: output,
- blocks: [codeBlockBlock, buttonsBlockRef.current],
+ blocks: [codeBlockBlock, buttonsBlockRef.current, buttonsCsvBlock()],
fallbackBlock: {
component: Markdown,
lookBack: markdownLookBack(),
@@ -326,7 +330,7 @@ export const ExampleSideBySideTokenArray: React.FC<
const [mobileTabIndex, setMobileTabIndex] = useState(0);
const [desktopTabIndex, setDesktopTabIndex] = useState(0);
- const buttonsBlockRef = useRef(buttonsBlock());
+ const buttonsBlockRef = useRef(buttonsJsonBlock());
const {
output,
isStreamFinished,
@@ -340,7 +344,7 @@ export const ExampleSideBySideTokenArray: React.FC<
const { finishCount, restart, blockMatches, isFinished, visibleText } =
useLLMOutput({
llmOutput: output,
- blocks: [codeBlockBlock, buttonsBlockRef.current],
+ blocks: [codeBlockBlock, buttonsBlockRef.current, buttonsCsvBlock()],
fallbackBlock: {
component: Markdown,
lookBack: markdownLookBack(),
diff --git a/apps/www/src/components/examples/examples.ts b/apps/www/src/components/examples/examples.ts
index b1bc2c8f..3decd197 100644
--- a/apps/www/src/components/examples/examples.ts
+++ b/apps/www/src/components/examples/examples.ts
@@ -59,7 +59,7 @@ export const presentationPauseExample: TokenWithDelay[] = [
...stringToTokenArray(afterPause, defaultExampleProbs),
];
-export const buttonPrompt = jsonBlockPrompt({
+export const buttonJsonPrompt = jsonBlockPrompt({
name: "Button",
schema: buttonsSchema,
examples: [
diff --git a/apps/www/src/content/docs/blocks/csv.mdx b/apps/www/src/content/docs/blocks/csv.mdx
index 9967d1c5..a9fea26c 100644
--- a/apps/www/src/content/docs/blocks/csv.mdx
+++ b/apps/www/src/content/docs/blocks/csv.mdx
@@ -3,4 +3,322 @@ title: CSV block
description: ""
---
-Coming soon. Come to discord to chat.
+import { ExampleSideBySide } from "@/components/examples/ExampleMdx";
+import { PackageInstall } from "@/components/docs/PackageInstall";
+import {
+ markdownQuickStart,
+ codeblockQuickstart,
+ llmUiOutputQuickStart,
+ llmUiOutputQuickStartStep,
+ fullCsvQuickStart,
+ jsonUseLlmOutput,
+ csvComponent,
+ generateCsvPromptCode,
+ fullCsvOptions,
+ getCsvPrompt,
+ generateCsvExampleCode,
+ getCsvExample,
+ csvUseLlmOutput,
+} from "../../../snippets/quickStart";
+import CopyDocsContainer from "@/components/content/CopyDocsContainer.astro";
+import CopyExampleButton from "@/components/content/CopyExampleButton.astro";
+import CopyOrGithub from "@/components/content/CopyOrGithub.astro";
+import { examplesUrl } from "@/constants/constants";
+
+Allow LLMs to reply with CSV, which can be rendered as custom components in your application.
+
+
+
+# Installation
+
+
+
+# Quick start
+
+
+
+## Install dependencies
+
+
+
+## Step 1: Create a markdown component
+
+Create a component to render markdown using `react-markdown`.
+
+
+
+
+ Read more in the [markdown block docs](/docs/blocks/markdown)
+
+
+## Step 2: Create a custom block component
+
+
+
+
+
+
+## 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.
+
+
+
+
+
+
+## Step 4: Prompt LLM with your custom block
+
+Generate the prompt for your JSON block:
+
+
+
+Generates:
+
+
+
+You can also hardcode the prompt into your application.
+
+# Options
+
+
+
+```tsx
+{
+ // Required:
+ type: "buttons", // the first item in the CSV
+ // Optional, defaults:
+ startChar: "⦅",
+ endChar: "⦆",
+ delimiter: ",", // the seperator between items
+ allIndexesVisible: true,
+ visibleIndexes: [],
+}
+```
+
+
+
+## `allIndexesVisible`
+
+### `allIndexesVisible: true`
+
+`type` is always skipped.
+
+Generates 'visibleText' as the reponse is parsed:
+
+
+
+```plain
+⦅buttons,Button 1,But
+```
+
+
+
+
+
+```tsx
+blockMatch.visibleText;
+// => "B"
+// then
+// => "Bu"
+// then
+// => "But"
+// later..
+// => "Button 1But"
+
+blockMatch.isVisible;
+// => true
+
+blockMatch.output;
+// => "buttons,B"
+// then
+// => "buttons,Bu"
+// then
+// => "buttons,But"
+// later..
+// => "buttons,Button 1,But"
+```
+
+
+
+### `allIndexesVisible: false`
+
+Generate no 'visibleText' until the whole block is parsed.
+
+When a partial block is parsed:
+
+
+
+```plain
+⦅buttons,Button 1,But`
+```
+
+
+
+
+
+```tsx
+blockMatch.visibleText;
+// => ""
+
+blockMatch.isVisible;
+// => false
+
+blockMatch.output;
+// => "buttons,Button 1,But"
+```
+
+
+
+When the whole block is parsed:
+
+
+
+```plain
+⦅buttons,Button 1,Button 2⦆
+```
+
+
+
+
+
+```tsx
+blockMatch.visibleText;
+// => " "
+
+blockMatch.isVisible;
+// => true
+
+blockMatch.output;
+// => "buttons,Button 1,Button 2"
+```
+
+
+
+## `visibleIndexes`
+
+You can use `visibleIndexes` with `allIndexesVisible: false` to determine which array indexes are visible.
+
+
+
+```tsx
+{
+ type: "buttons",
+ allIndexesVisible: false,
+ visibleIndexes: [1], // only the first item (after the type) is 'visible'
+}
+```
+
+
+
+# Prompts
+
+## `csvBlockPrompt`
+
+Returns a full prompt to send to the LLM.
+
+
+
+Generates:
+
+
+
+You can also hardcode the prompt into your application.
+
+## `csvBlockExample`
+
+Returns a single CSV block usage example.
+
+
+
+Generates:
+
+
+
+# CSV block functions
+
+## `csvBlock`
+
+Returns a CSV block object to be used by [`useLLMOutput`](/docs/llm-output-hook#blocks-object).
+
+```tsx
+import { csvBlock } from "@llm-ui/csv";
+
+const options = {
+ type: "buttons", // the first item in the CSV
+ delimiter: ";",
+ startChar: "[",
+ endChar: "]",
+};
+
+csvBlock(options);
+// =>
+{
+ findCompleteMatch: findCompleteCsvBlock(options),
+ findPartialMatch: findPartialCsvBlock(options),
+ lookBack: csvBlockLookBack(options),
+ component: () => Json block
,
+}
+```
+
+Accepts [options](#options) parameter.
+
+## `findCompleteCsvBlock`
+
+Finds a [complete CSV block](/docs/llm-output-hook#blocks-object) in a string.
+
+For example:
+
+```
+⦅buttons,Button 1,Button2⦆
+```
+
+Accepts [options](#options) parameter.
+
+## `findPartialCsvBlock`
+
+Find a [partial CSV block](/docs/llm-output-hook#blocks-object) in a string.
+
+For example:
+
+```
+⦅buttons,Button 1,But
+```
+
+## `csvBlockLookBack`
+
+[Look back function](/docs/llm-output-hook#blocks-object) for the CSV block.
+
+Accepts [options](#options) parameter.
+
+# Parse
+
+## `parseCsv`
+
+Parse a CSV output string.
+
+
+
+```tsx
+import { parseCsv } from "@llm-ui/csv";
+
+parseCsv("buttons,Button 1,Button2");
+// =>
+["buttons", "Button 1", "Button 2"];
+```
+
+
diff --git a/apps/www/src/content/docs/blocks/json.mdx b/apps/www/src/content/docs/blocks/json.mdx
index 45753d54..c883dd03 100644
--- a/apps/www/src/content/docs/blocks/json.mdx
+++ b/apps/www/src/content/docs/blocks/json.mdx
@@ -3,7 +3,7 @@ title: JSON Block
description: ""
---
-import { buttonPrompt } from "@/components/examples/examples";
+import { buttonJsonPrompt } from "@/components/examples/examples";
import { ExampleSideBySide } from "@/components/examples/ExampleMdx";
import { PackageInstall } from "@/components/docs/PackageInstall";
import {
@@ -11,11 +11,11 @@ import {
codeblockQuickstart,
llmUiOutputQuickStart,
llmUiOutputQuickStartStep,
- fullCustomQuickStart,
- customJsonSchema,
- customButtonsComponent,
- customButtonsUseLlmOutput,
- generateButtonsPrompt,
+ fullJsonQuickStart,
+ jsonSchema,
+ jsonComponent,
+ jsonUseLlmOutput,
+ generateJsonPrompt,
} from "../../../snippets/quickStart";
import CopyDocsContainer from "@/components/content/CopyDocsContainer.astro";
import CopyExampleButton from "@/components/content/CopyExampleButton.astro";
@@ -24,8 +24,6 @@ import { examplesUrl } from "@/constants/constants";
Allow LLMs to reply with JSON, which can be rendered as custom components in your application.
-## Demo
-
@@ -64,7 +62,7 @@ Create a component to render markdown using `react-markdown`.
-
+
Read more in the [markdown block docs](/docs/blocks/markdown)
@@ -74,9 +72,9 @@ Use [zod](https://zod.dev/?id=objects) to create a schema for your custom block.
We'll set up a 'buttons' block:
-
+
-
+
Example JSON for your block:
@@ -94,29 +92,31 @@ Example JSON for your block:
## Step 3: Create a custom block component
-
+
-
+
## Step 4: 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.
-
+
-
+
## Step 5: Prompt LLM with your custom block
Generate the prompt for your JSON block:
-
+
Generates:
-
+
+
+You can also hardcode the prompt into your
# Options
@@ -124,10 +124,12 @@ Generates:
```tsx
{
- type: "buttons", // required
+ // Required
+ type: "buttons",
+ // Optional, defaults:
startChar: "【",
endChar: "】",
- typeKey: "type" // the key in the JSON object which determines the block type e.g. {"type": "buttons"}
+ typeKey: "type", // the key in the JSON object which determines the block type e.g. {"type": "buttons"}
defaultVisible: false, // See below
visibleKeyPaths: [], // See below
invisibleKeyPaths: [], // See below
@@ -273,7 +275,7 @@ jsonBlockPrompt({
schema: z.object({
type: z.literal("buttons"),
buttons: z.array(z.object({ text: z.string() })),
- });
+ }),
examples: [
{ type: "buttons", buttons: [{ text: "Button 1" }, { text: "Button 2" }] },
],
@@ -464,4 +466,4 @@ Becomes:
【{"t":"b","bs":[{"t":"Button 1"},{"t":...
```
-You could also consider using the [seperated values block](/docs/blocks/seperated-values) for a lower overhead format.
+You could also consider using the [CSV block](/docs/blocks/csv) for a lower overhead format.
diff --git a/apps/www/src/content/docs/custom-blocks.mdx b/apps/www/src/content/docs/custom-blocks.mdx
index f899f74f..c3ca15b9 100644
--- a/apps/www/src/content/docs/custom-blocks.mdx
+++ b/apps/www/src/content/docs/custom-blocks.mdx
@@ -31,7 +31,7 @@ Custom blocks allow LLMs to reply with specific formats which can be rendered as
```plain
-⦅Button 1,Button 2⦆
+⦅buttons,Button 1,Button 2⦆
```
diff --git a/apps/www/src/content/docs/examples.mdx b/apps/www/src/content/docs/examples.mdx
index fc396f6b..8650bb09 100644
--- a/apps/www/src/content/docs/examples.mdx
+++ b/apps/www/src/content/docs/examples.mdx
@@ -28,7 +28,13 @@ An example of how to use the code block with Shiki.
An example of how to use the Json block to build your own custom components.
-
+
+
+# Csv block example
+
+An example of how to use the Csv block to build your own custom components.
+
+
# OpenAI example
diff --git a/apps/www/src/snippets/quickStart.ts b/apps/www/src/snippets/quickStart.ts
index 2285cf5e..59a56c01 100644
--- a/apps/www/src/snippets/quickStart.ts
+++ b/apps/www/src/snippets/quickStart.ts
@@ -1,3 +1,10 @@
+import {
+ csvBlockExample,
+ csvBlockPrompt,
+ type CsvBlockOptions,
+} from "@llm-ui/csv";
+import prettier from "@prettier/sync";
+
export const markdownImports = `import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { type LLMOutputComponent } from "@llm-ui/react";
@@ -175,7 +182,7 @@ const step3Comment =
export const fullQuickStart = `${markdownAndCodeblockImports}\n\n${step1Comment}\n\n${markdownComponent}\n\n${step2Comment}\n\n${codeblockComponent}\n\n${step3Comment}\n\n${llmUiOutputUsage}\n\nexport default Example`;
-export const customJsonSchema = `// buttonsSchema.ts
+export const jsonSchema = `// buttonsSchema.ts
import z from "zod";
const buttonsSchema = z.object({
@@ -184,13 +191,15 @@ const buttonsSchema = z.object({
});
`;
-export const customButtonsComponent = `import { parseJson5 } from "@llm-ui/json";
+export const jsonComponent = `import { parseJson5 } from "@llm-ui/json";
import { type LLMOutputComponent } from "@llm-ui/react";
+// Customize this component with your own styling
const ButtonsComponent: LLMOutputComponent = ({ blockMatch }) => {
if (!blockMatch.isVisible) {
return null;
}
+ // use buttonsSchema from step 2
const { data: buttons, error } = buttonsSchema.safeParse(
parseJson5(blockMatch.output),
);
@@ -208,11 +217,8 @@ const ButtonsComponent: LLMOutputComponent = ({ blockMatch }) => {
};
`;
-export const customButtonsUseLlmOutput = `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";
+export const jsonUseLlmOutput = `import { markdownLookBack } from "@llm-ui/markdown";
+import { useLLMOutput, useStreamExample } from "@llm-ui/react";
import { jsonBlock } from "@llm-ui/json";
const example = \`Buttons:
@@ -220,47 +226,51 @@ const example = \`Buttons:
【{type:"buttons",buttons:[{text:"Star ⭐"}, {text:"Confetti 🎉"}]}】
\`;
-const { isStreamFinished, output } = useStreamExample(example);
+const Example = () => {
+ const { isStreamFinished, output } = useStreamExample(example);
-const { blockMatches } = useLLMOutput({
- llmOutput: output,
- blocks: [
- {
- ...jsonBlock({type: "buttons"}),
- component: ButtonsComponent, // from step 3
+ const { blockMatches } = useLLMOutput({
+ llmOutput: output,
+ blocks: [
+ {
+ ...jsonBlock({type: "buttons"}),
+ component: ButtonsComponent, // from step 3
+ },
+ ],
+ fallbackBlock: {
+ lookBack: markdownLookBack(),
+ component: MarkdownComponent, // from step 1
},
- ],
- fallbackBlock: {
- lookBack: markdownLookBack(),
- component: MarkdownComponent, // from step 1
- },
- isStreamFinished,
-});
+ isStreamFinished,
+ });
-return (
-
- {blockMatches.map((blockMatch, index) => {
- const Component = blockMatch.block.component;
- return ;
- })}
-
-);
+ return (
+
+ {blockMatches.map((blockMatch, index) => {
+ const Component = blockMatch.block.component;
+ return ;
+ })}
+
+ );
+}
`;
-export const generateButtonsPrompt = `import { jsonBlockPrompt } from "@llm-ui/json";
+export const generateJsonPrompt = `import { jsonBlockPrompt } from "@llm-ui/json";
const prompt = jsonBlockPrompt({
name: "Button",
- schema: buttonsSchema, // use schema from step 2
+ schema: buttonsSchema,
examples: [
{ type: "buttons", buttons: [{ text: "Button 1" }, { text: "Button 2" }] },
- ]
-});
-`;
+ ],
+ options: {
+ type: "buttons",
+ },
+});`;
-export const fullCustomQuickStart = `import { jsonBlock, jsonBlockPrompt, parseJson5 } from "@llm-ui/json";
+export const fullJsonQuickStart = `import { jsonBlock, jsonBlockPrompt, parseJson5 } from "@llm-ui/json";
import { markdownLookBack } from "@llm-ui/markdown";
import {
useLLMOutput,
@@ -326,7 +336,7 @@ const example = \`
## Example
more text 123
more text 123
-【{type:"buttons",buttons:null}】
+【{"type":"buttons","buttons":[{"text":"Button 1"},{"text":"Button 2"}]}】
one more time
\`;
@@ -360,3 +370,195 @@ const Example = () => {
export default Example;
`;
+
+export const csvComponent = `import { type LLMOutputComponent } from "@llm-ui/react";
+import { parseCsv } from '@llm-ui/csv'
+
+// Customize this component with your own styling
+const ButtonsComponent: LLMOutputComponent = ({ blockMatch }) => {
+ if (!blockMatch.isVisible) {
+ return null;
+ }
+ const [_type, ...buttons] = parseCsv(blockMatch.output, {type: 'buttons'});
+
+ return (
+
+ {buttons.map((buttonText, index) => (
+
+ ))}
+
+ );
+};`;
+
+export const csvUseLlmOutput = `import { csvBlock } from "@llm-ui/csv";
+import { markdownLookBack } from "@llm-ui/markdown";
+import {
+ useLLMOutput,
+ useStreamExample,
+} from "@llm-ui/react";
+
+const example = \`
+Buttons
+⦅buttons,Button 1,Button2⦆
+\`;
+
+const Example = () => {
+ const { isStreamFinished, output } = useStreamExample(example);
+
+ const { blockMatches } = useLLMOutput({
+ llmOutput: output,
+ blocks: [
+ {
+ ...csvBlock({type: 'buttons'}), // from step 2
+ component: ButtonsComponent,
+ },
+ ],
+ fallbackBlock: {
+ component: MarkdownComponent, // from step 1
+ lookBack: markdownLookBack(),
+ },
+ isStreamFinished,
+ });
+ return (
+
+ {blockMatches.map((blockMatch, index) => {
+ const Component = blockMatch.block.component;
+ return ;
+ })}
+
+ );
+};
+
+`;
+
+export const getCsvPromptOptions = (
+ options: CsvBlockOptions = { type: "buttons" },
+) => ({
+ name: "Buttons",
+ examples: [["Button 1", "Button 2"]],
+ options,
+});
+
+export const fullCsvOptions = {
+ type: "buttons",
+ delimiter: ";",
+ startChar: "[",
+ endChar: "]",
+};
+
+export const generateCsvPromptCode = (
+ options: CsvBlockOptions = { type: "buttons" },
+) =>
+ prettier.format(
+ `import { csvBlockPrompt } from "@llm-ui/csv";
+
+const prompt = csvBlockPrompt(${JSON.stringify(getCsvPromptOptions(options))});`,
+ { parser: "typescript", printWidth: 60 },
+ );
+
+export const getCsvPrompt = (options: CsvBlockOptions | undefined) =>
+ csvBlockPrompt(getCsvPromptOptions(options));
+
+export const generateCsvExampleCode = (
+ options: CsvBlockOptions = { type: "buttons" },
+) =>
+ prettier.format(
+ `import { csvBlockExample } from "@llm-ui/csv";
+
+const prompt = csvBlockExample(["Button 1", "Button 2"], ${JSON.stringify(options)});`,
+ { parser: "typescript", printWidth: 60 },
+ );
+
+export const getCsvExample = (options: CsvBlockOptions = { type: "buttons" }) =>
+ csvBlockExample(["Button 1", "Button 2"], options);
+
+export const fullCsvQuickStart = `import { csvBlock, csvBlockPrompt, parseCsv } from "@llm-ui/csv";
+import { markdownLookBack } from "@llm-ui/markdown";
+import {
+ useLLMOutput,
+ useStreamExample,
+ type LLMOutputComponent,
+} from "@llm-ui/react";
+
+import ReactMarkdown from "react-markdown";
+import remarkGfm from "remark-gfm";
+
+const options = {
+ type: "buttons",
+};
+
+const buttonsPrompt = csvBlockPrompt({
+ name: "buttons",
+ examples: [["Button 1", "Button 2"]],
+ options,
+});
+// -------Step 1: Create a markdown component-------
+
+// Customize this component with your own styling
+const MarkdownComponent: LLMOutputComponent = ({ blockMatch }) => {
+ const markdown = blockMatch.output;
+ return (
+
+ {markdown}
+
+ );
+};
+
+// -------Step 2: Create a buttons component-------
+
+// Customize this component with your own styling
+const ButtonsComponent: LLMOutputComponent = ({ blockMatch }) => {
+ if (!blockMatch.isVisible) {
+ return null;
+ }
+ const [_type, ...buttons] = parseCsv(blockMatch.output, options);
+
+ return (
+
+ {buttons.map((buttonText, index) => (
+
+ ))}
+
+ );
+};
+
+// -------Step 3: Render markdown with llm-ui-------
+
+const example = \`
+## Example
+ more text 123
+ more text 123
+⦅buttons,Button 1,Button2⦆
+one more time
+\`;
+
+const Example = () => {
+ const { isStreamFinished, output } = useStreamExample(example);
+
+ const { blockMatches } = useLLMOutput({
+ llmOutput: output,
+ blocks: [
+ {
+ ...csvBlock(options),
+ component: ButtonsComponent,
+ },
+ ],
+ fallbackBlock: {
+ component: MarkdownComponent,
+ lookBack: markdownLookBack(),
+ },
+ isStreamFinished,
+ });
+ return (
+
+
Prompt: {buttonsPrompt}
+ {blockMatches.map((blockMatch, index) => {
+ const Component = blockMatch.block.component;
+ return
;
+ })}
+
+ );
+};
+
+export default Example;
+`;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5d6df561..42046b6d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -107,6 +107,9 @@ importers:
'@llm-ui/code':
specifier: workspace:*
version: link:../../packages/code
+ '@llm-ui/csv':
+ specifier: workspace:*
+ version: link:../../packages/csv
'@llm-ui/examples':
specifier: workspace:*
version: link:../../tooling/examples
@@ -119,6 +122,9 @@ importers:
'@llm-ui/react':
specifier: workspace:*
version: link:../../packages/react
+ '@prettier/sync':
+ specifier: ^0.5.2
+ version: 0.5.2(prettier@3.2.5)
'@radix-ui/react-accordion':
specifier: ^1.1.2
version: 1.1.2(@types/react-dom@18.2.23)(@types/react@18.2.73)(react-dom@18.2.0)(react@18.2.0)
@@ -3373,6 +3379,15 @@ packages:
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
dev: true
+ /@prettier/sync@0.5.2(prettier@3.2.5):
+ resolution: {integrity: sha512-Yb569su456XNx5BsH/Vyem7xD6g/y9iLmLUzRKM1a/dhU/D7HqqvkAG72znulXlMXztbV0iiu9O5AL8K98TzZQ==}
+ peerDependencies:
+ prettier: '*'
+ dependencies:
+ make-synchronized: 0.2.9
+ prettier: 3.2.5
+ dev: false
+
/@radix-ui/number@1.0.1:
resolution: {integrity: sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==}
dependencies:
@@ -9480,6 +9495,10 @@ packages:
semver: 6.3.1
dev: false
+ /make-synchronized@0.2.9:
+ resolution: {integrity: sha512-4wczOs8SLuEdpEvp3vGo83wh8rjJ78UsIk7DIX5fxdfmfMJGog4bQzxfvOwq7Q3yCHLC4jp1urPHIxRS/A93gA==}
+ dev: false
+
/map-obj@1.0.1:
resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==}
engines: {node: '>=0.10.0'}
@@ -11119,7 +11138,6 @@ packages:
resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==}
engines: {node: '>=14'}
hasBin: true
- dev: true
/pretty-format@29.7.0:
resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}