From b264096e3d8810989d0269efa46225da2d7677b5 Mon Sep 17 00:00:00 2001 From: Richard Gill Date: Thu, 30 May 2024 17:57:42 +0100 Subject: [PATCH] [Docs] CSV Docs (#232) --- .npmrc | 2 + apps/www/package.json | 4 +- apps/www/src/animations/buttonHandler.ts | 10 + .../src/components/demo/message/Message.tsx | 8 +- .../src/components/examples/ButtonsCsv.tsx | 38 +++ .../examples/{Buttons.tsx => ButtonsJson.tsx} | 13 +- apps/www/src/components/examples/Example.tsx | 14 +- apps/www/src/components/examples/examples.ts | 2 +- apps/www/src/content/docs/blocks/csv.mdx | 320 +++++++++++++++++- apps/www/src/content/docs/blocks/json.mdx | 46 +-- apps/www/src/content/docs/custom-blocks.mdx | 2 +- apps/www/src/content/docs/examples.mdx | 8 +- apps/www/src/snippets/quickStart.ts | 274 +++++++++++++-- pnpm-lock.yaml | 20 +- 14 files changed, 678 insertions(+), 83 deletions(-) create mode 100644 .npmrc create mode 100644 apps/www/src/animations/buttonHandler.ts create mode 100644 apps/www/src/components/examples/ButtonsCsv.tsx rename apps/www/src/components/examples/{Buttons.tsx => ButtonsJson.tsx} (76%) 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==}