From a3ee83821071e51ff7f3c0959af1e4f08ed84bbb Mon Sep 17 00:00:00 2001 From: Visharad Kashyap <154831195+vishxrad@users.noreply.github.com> Date: Tue, 29 Sep 2026 14:19:22 +0530 Subject: [PATCH 1/3] refactor(cookbooks): answer with the chat library The cookbooks built each library from string lookups into openuiLibrary, with a Stack root, plus FollowUpBlock and FollowUpItem borrowed from openuiChatLibrary. The chat library is the one made for Agent Interface answers and already has every component the cookbooks picked, so: - Document comparison and conversational analytics export openuiChatLibrary unchanged. - Booking assistant uses the chat library with its DatePicker replaced. - Document comparison drops its custom Sources component and passes the sources as the chat Card's second argument, which renders the same source strip. - Prompt examples and rules use a Card root instead of Stack. Co-Authored-By: Claude Opus 5.5 --- .../docs/cookbooks/booking-assistant.mdx | 41 ++++---------- .../cookbooks/conversational-analytics.mdx | 26 ++------- .../docs/cookbooks/document-comparison.mdx | 53 ++++--------------- .../cookbooks/booking-assistant/README.md | 2 +- .../src/components/date-picker.tsx | 4 +- .../booking-assistant/src/lib/prompt.ts | 6 +-- .../booking-assistant/src/library.ts | 36 +++---------- .../conversational-analytics/README.md | 4 +- .../src/lib/prompt.ts | 7 +-- .../conversational-analytics/src/library.ts | 25 +-------- .../cookbooks/document-comparison/README.md | 3 +- .../src/components/sources.tsx | 35 ------------ .../document-comparison/src/lib/prompt.ts | 14 ++--- .../document-comparison/src/library.ts | 29 +--------- 14 files changed, 53 insertions(+), 232 deletions(-) delete mode 100644 examples/cookbooks/document-comparison/src/components/sources.tsx diff --git a/docs/content/docs/cookbooks/booking-assistant.mdx b/docs/content/docs/cookbooks/booking-assistant.mdx index d504da3d4..301605aa1 100644 --- a/docs/content/docs/cookbooks/booking-assistant.mdx +++ b/docs/content/docs/cookbooks/booking-assistant.mdx @@ -176,47 +176,28 @@ The result holds the top six stays and, when the budget left some out, how many ### 3. Choose the components -Use the built-in form controls, a few components for summaries and messages, and one component of your own: +Use the chat library, React UI's built-in library for Agent Interface answers. It has the form controls, summaries, and messages a booking needs, so the example replaces only its date picker: ```ts title="src/library.ts" +import { createLibrary } from "@openuidev/react-lang"; +import { openuiChatLibrary } from "@openuidev/react-ui/genui-lib"; +import { DatePicker } from "./components/date-picker"; + export const library = createLibrary({ - root: "Stack", - components: [ - ...[ - "Stack", - "CardHeader", - "TextContent", - "Callout", - "Table", - "Col", - "Form", - "FormControl", - "Input", - "Select", - "SelectItem", - "Chips", - "ChipItem", - "OptionCards", - "OptionCard", - "Image", - "Buttons", - "Button", - ].map((name) => openuiLibrary.components[name]), - DatePicker, - openuiChatLibrary.components.FollowUpBlock, - openuiChatLibrary.components.FollowUpItem, - ], + root: openuiChatLibrary.root, + componentGroups: openuiChatLibrary.componentGroups, + components: Object.values({ ...openuiChatLibrary.components, DatePicker }), }); ``` -`Form` groups the fields and checks their validation rules when the person clicks a primary button. `Chips` covers short choices, such as stars and amenities. `OptionCards` shows the stays as selectable cards, each with an `Image` of the hotel. By default, an option card shows its image as a small thumbnail; one CSS rule in [styles.css](https://github.com/thesysdev/openui/blob/main/examples/cookbooks/booking-assistant/src/app/styles.css) stretches it across the card. +Every answer is one `Card`. `Form` groups the fields and checks their validation rules when the person clicks a primary button. `Chips` covers short choices, such as stars and amenities. `OptionCards` shows the stays as selectable cards, each with an `Image` of the hotel. By default, an option card shows its image as a small thumbnail; one CSS rule in [styles.css](https://github.com/thesysdev/openui/blob/main/examples/cookbooks/booking-assistant/src/app/styles.css) stretches it across the card. React UI's own `DatePicker` stores JavaScript `Date` objects. The model can only write text, so it cannot prefill one, and a submitted `Date` reaches the model as a UTC timestamp that can fall on the day before. The example replaces it with a component that has the same name and props but reads and writes `YYYY-MM-DD` strings: ```tsx title="src/components/date-picker.tsx" export const DatePicker = defineComponent({ name: "DatePicker", - props: openuiLibrary.components.DatePicker.props, + props: openuiChatLibrary.components.DatePicker.props, description: "A single date. Prefill it by passing a YYYY-MM-DD string as value; " + "the form submits YYYY-MM-DD.", @@ -233,7 +214,7 @@ export const DatePicker = defineComponent({ }); ``` -Reusing the built-in props keeps `DatePicker` valid inside `FormControl`, and the new description tells the model the format. The [complete component](https://github.com/thesysdev/openui/blob/main/examples/cookbooks/booking-assistant/src/components/date-picker.tsx) also registers validation rules and closes the calendar once a date is picked. Generate the server specification from the library with `npm run generate`, which also runs before development and builds. +Spreading it after the chat library's components replaces the built-in `DatePicker`, which has the same name. Reusing the built-in props keeps it valid inside `FormControl`, and the new description tells the model the format. The [complete component](https://github.com/thesysdev/openui/blob/main/examples/cookbooks/booking-assistant/src/components/date-picker.tsx) also registers validation rules and closes the calendar once a date is picked. Generate the server specification from the library with `npm run generate`, which also runs before development and builds. ### 4. Describe the booking flow diff --git a/docs/content/docs/cookbooks/conversational-analytics.mdx b/docs/content/docs/cookbooks/conversational-analytics.mdx index 335db9f04..8e2e0c38d 100644 --- a/docs/content/docs/cookbooks/conversational-analytics.mdx +++ b/docs/content/docs/cookbooks/conversational-analytics.mdx @@ -140,33 +140,13 @@ The query in the same file uses parameterized SQL. Missing times are left out of ### 3. Choose the components -Use a small selection from the built-in library, plus the chat library's follow-up suggestions: +Use the chat library, React UI's built-in library for Agent Interface answers. Its tables, charts, and follow-up suggestions cover every answer, so the example uses it as is: ```ts title="src/library.ts" -import { createLibrary } from "@openuidev/react-lang"; -import { openuiChatLibrary, openuiLibrary } from "@openuidev/react-ui/genui-lib"; - -export const library = createLibrary({ - root: "Stack", - components: [ - ...[ - "Stack", - "Card", - "CardHeader", - "TextContent", - "LineChart", - "BarChart", - "Series", - "Table", - "Col", - ].map((name) => openuiLibrary.components[name]), - openuiChatLibrary.components.FollowUpBlock, - openuiChatLibrary.components.FollowUpItem, - ], -}); +export { openuiChatLibrary as library } from "@openuidev/react-ui/genui-lib"; ``` -Clicking a follow-up sends its text as the next question. +Every answer is one `Card`: a `Table` or `BarChart` for a ranking, a `LineChart` for a lap-by-lap comparison, and a `FollowUpBlock` at the end. Clicking a follow-up sends its text as the next question. Generate the server specification from the same library used by the renderer: diff --git a/docs/content/docs/cookbooks/document-comparison.mdx b/docs/content/docs/cookbooks/document-comparison.mdx index a882139d4..2daa34fb8 100644 --- a/docs/content/docs/cookbooks/document-comparison.mdx +++ b/docs/content/docs/cookbooks/document-comparison.mdx @@ -175,55 +175,20 @@ The example keeps all 1,564 embeddings in memory and compares them in JavaScript ### 3. Choose the components -Use a selection from the built-in library that covers every part of a comparison, the chat library's follow-up suggestions, and one component of your own: +Use the chat library, React UI's built-in library for Agent Interface answers. It already has every part of a comparison, so the example uses it as is: ```ts title="src/library.ts" -import { createLibrary } from "@openuidev/react-lang"; -import { openuiChatLibrary, openuiLibrary } from "@openuidev/react-ui/genui-lib"; -import { Sources } from "./components/sources"; - -export const library = createLibrary({ - root: "Stack", - components: [ - ...[ - "Stack", - "CardHeader", - "TextContent", - "Table", - "Col", - "BarChart", - "HorizontalBarChart", - "LineChart", - "Series", - "Callout", - "TagBlock", - ].map((name) => openuiLibrary.components[name]), - Sources, - openuiChatLibrary.components.FollowUpBlock, - openuiChatLibrary.components.FollowUpItem, - ], -}); +export { openuiChatLibrary as library } from "@openuidev/react-ui/genui-lib"; ``` -`Table` holds the page-cited values, the three chart types cover single values, trends, and breakdowns, and `Callout` flags gaps and conflicts. - -`Sources` wraps the source strip that React UI already provides. `defineComponent` gives it a name, a schema the model fills in, and a description for the prompt: - -```tsx title="src/components/sources.tsx" -export const Sources = defineComponent({ - name: "Sources", - props: z.object({ items: z.array(CardSourceSchema) }), - description: - "A strip of source cards. Each item is { title, sourceName, url }: title is the quote, sourceName names the document and page, and url opens the page.", - component: ({ props }) => ( - - - - ), -}); +Every answer is one `Card`. `Table` holds the page-cited values, `BarChart`, `LineChart`, and `HorizontalBarChart` cover single values, trends, and breakdowns, `Callout` flags gaps and conflicts, and `FollowUpBlock` suggests criteria to add. The `Card`'s second argument lists the sources, and the `Card` renders them as a strip of source cards below the answer: + +```text +root = Card([header, table, chart, gap, followUps], sources) +sources = [{title: "“Revenue for the fiscal year was $10.0 billion, up 5% from a year ago.”", sourceName: "Company A · Page 12", url: "https://example.com/company-a.pdf#page=12"}] ``` -Each card shows the site's icon, the company and page, and the quote. `onePerPage` merges two quotes from the same page into one card. Generate the server specification from the same library with `npm run generate`, which also runs before development and builds. +Each card shows the site's icon, the company and page, and the quote, and opens the report at that page. The prompt names the components an answer uses and rules out form controls. Generate the server specification from the same library with `npm run generate`, which also runs before development and builds. ### 4. Customize Agent Interface @@ -347,7 +312,7 @@ The prompt turns the evidence into a consistent answer: - **A table** with one short, page-cited value per cell, such as “$215.9 billion (p. 37)”: one row per criterion and one column per document, or one row per part when a criterion has several, such as each company's segments. - **Charts chosen from the data**, each at full width: a line chart when a report gives several years of figures, one horizontal bar chart with a bar per company and segment for a breakdown, and a bar chart for one value per document. A document without a value is left out of the chart rather than plotted as zero. - **Callouts** for information that conflicts or is not directly comparable, such as different fiscal year ends, and for criteria a document does not disclose. -- **Sources**, one card per quoted page with the company, page, and a quote of at most 20 words, linked to that page of the PDF. +- **Sources** in the `Card`'s second argument, one per quoted page with the company, page, and a quote of at most 20 words, linked to that page of the PDF. ## Verify it works diff --git a/examples/cookbooks/booking-assistant/README.md b/examples/cookbooks/booking-assistant/README.md index f3a607a2f..8bc3f4eeb 100644 --- a/examples/cookbooks/booking-assistant/README.md +++ b/examples/cookbooks/booking-assistant/README.md @@ -40,7 +40,7 @@ Try: | ------------------------------------- | --------------------------------------------------------------------- | | `src/lib/trivago.ts` | MCP client for trivago's accommodation search, and its filter options | | `src/lib/tools/search-stays.ts` | Function schema, argument validation, budget filter, and results | -| `src/library.ts` | Shared components for the prompt and renderer | +| `src/library.ts` | The chat library, with its DatePicker replaced | | `src/components/date-picker.tsx` | A DatePicker that the model can prefill with YYYY-MM-DD dates | | `src/lib/prompt.ts` | Booking rules and one example for each step of the flow | | `src/lib/gateway-history.ts` | Loads a thread's stored turns from Gateway as chat messages | diff --git a/examples/cookbooks/booking-assistant/src/components/date-picker.tsx b/examples/cookbooks/booking-assistant/src/components/date-picker.tsx index 3f569f5b8..e0555344f 100644 --- a/examples/cookbooks/booking-assistant/src/components/date-picker.tsx +++ b/examples/cookbooks/booking-assistant/src/components/date-picker.tsx @@ -8,7 +8,7 @@ import { useStateField, } from "@openuidev/react-lang"; import { DatePicker as DateField } from "@openuidev/react-ui"; -import { openuiLibrary } from "@openuidev/react-ui/genui-lib"; +import { openuiChatLibrary } from "@openuidev/react-ui/genui-lib"; import { useEffect, useMemo, useState } from "react"; // React UI's DatePicker stores Date objects: the model cannot prefill one, and a submitted @@ -28,7 +28,7 @@ const toIsoDate = (date?: Date) => export const DatePicker = defineComponent({ name: "DatePicker", - props: openuiLibrary.components.DatePicker.props, + props: openuiChatLibrary.components.DatePicker.props, description: "A single date. Prefill it by passing a YYYY-MM-DD string as value; the form submits YYYY-MM-DD.", component: ({ props }) => { diff --git a/examples/cookbooks/booking-assistant/src/lib/prompt.ts b/examples/cookbooks/booking-assistant/src/lib/prompt.ts index 99e2f8b5e..d37c99069 100644 --- a/examples/cookbooks/booking-assistant/src/lib/prompt.ts +++ b/examples/cookbooks/booking-assistant/src/lib/prompt.ts @@ -17,7 +17,7 @@ const day = (date: string) => function examples(today: string) { const friday = addDays(today, (5 - new Date(today).getUTCDay() + 7) % 7); const sunday = addDays(friday, 2); - const tripForm = `root = Stack([intro, trip], "column", "m") + const tripForm = `root = Card([intro, trip]) intro = TextContent("Here's what I understood. Fill in anything missing, then find stays.") trip = Form("trip", tripButtons, [destination, checkIn, checkOut, adults, budget, stars, rating, musts]) tripButtons = Buttons([Button("Find stays", Action([@ToAssistant("Find stays")]), "primary")]) @@ -30,13 +30,13 @@ stars = FormControl("Hotel stars", Chips("stars", "multiple", [ChipItem("3", "3 rating = FormControl("Guest rating", Chips("min_guest_rating", "single", [ChipItem("any", "Any"), ChipItem("8.0", "8.0+"), ChipItem("8.5", "8.5+")], null, "any")) musts = FormControl("Must-haves", Chips("amenities", "multiple", [ChipItem("freeWiFi", "Free WiFi"), ChipItem("breakfastIncluded", "Breakfast included"), ChipItem("freeCancellation", "Free cancellation"), ChipItem("pool", "Pool")])) `; - const chooseStay = `root = Stack([header, choose], "column", "m") + const chooseStay = `root = Card([header, choose]) header = CardHeader("Stays in Lisbon for your dates", "${day(friday)} – ${day(sunday)} · 2 nights · 2 adults · 4★") choose = Form("choose", chooseButtons, [pick]) chooseButtons = Buttons([Button("Review stay", Action([@ToAssistant("Review stay")]), "primary"), Button("Change search", Action([@ToAssistant("Change search")]), "secondary")]) pick = FormControl("Choose a stay", OptionCards("stay", "single", [OptionCard("https://www.trivago.com/example-riverside-deal", "Riverside Hotel", "€215 a night · €429 total · 4★ · 8.3 (7,960 reviews) · 0.2 km to Praça do Comércio · via Hotel Site", Image("Riverside Hotel", "https://example.com/riverside.jpg")), OptionCard("https://www.trivago.com/example-castle-deal", "Castle View Suites", "€176 a night · €353 total · 4★ · 8.4 (623 reviews) · 0.4 km to the castle · via Booking.com", Image("Castle View Suites", "https://example.com/castle.jpg"))], {required: true})) `; - const review = `root = Stack([header, details, handoff, actions], "column", "m") + const review = `root = Card([header, details, handoff, actions]) header = CardHeader("Review your stay", "Riverside Hotel · Lisbon") details = Table([Col("Detail", ["Check-in", "Check-out", "Guests", "Hotel", "Price per night", "Total", "Book on"]), Col("Value", ["${day(friday)}", "${day(sunday)} · 2 nights", "2 adults · 1 room", "4★ · 8.3 guest rating", "€215", "€429", "Hotel Site"])]) handoff = Callout("info", "You'll finish booking on Hotel Site", "trivago found this price. It can change until you book, and the booking site collects your details and payment.") diff --git a/examples/cookbooks/booking-assistant/src/library.ts b/examples/cookbooks/booking-assistant/src/library.ts index e03238e59..987d90139 100644 --- a/examples/cookbooks/booking-assistant/src/library.ts +++ b/examples/cookbooks/booking-assistant/src/library.ts @@ -1,35 +1,11 @@ import { createLibrary } from "@openuidev/react-lang"; -import { openuiChatLibrary, openuiLibrary } from "@openuidev/react-ui/genui-lib"; +import { openuiChatLibrary } from "@openuidev/react-ui/genui-lib"; import { DatePicker } from "./components/date-picker"; -// The same subset generates the server's specification and renders in the client. +// The chat library generates the server's specification and renders in the client. +// Its DatePicker is replaced so dates can be prefilled and submitted as YYYY-MM-DD. export const library = createLibrary({ - root: "Stack", - components: [ - ...[ - "Stack", - "CardHeader", - "TextContent", - "Callout", - "Table", - "Col", - "Form", - "FormControl", - "Input", - "Select", - "SelectItem", - "Chips", - "ChipItem", - "OptionCards", - "OptionCard", - "Image", - "Buttons", - "Button", - ].map((name) => openuiLibrary.components[name]), - // Replaces React UI's DatePicker so dates can be prefilled and submitted as YYYY-MM-DD. - DatePicker, - // Follow-up suggestions live in the chat library. - openuiChatLibrary.components.FollowUpBlock, - openuiChatLibrary.components.FollowUpItem, - ], + root: openuiChatLibrary.root, + componentGroups: openuiChatLibrary.componentGroups, + components: Object.values({ ...openuiChatLibrary.components, DatePicker }), }); diff --git a/examples/cookbooks/conversational-analytics/README.md b/examples/cookbooks/conversational-analytics/README.md index b1b096260..00a1d85a4 100644 --- a/examples/cookbooks/conversational-analytics/README.md +++ b/examples/cookbooks/conversational-analytics/README.md @@ -44,7 +44,7 @@ The tool supports `fastest_laps` (one best recorded lap per driver) and `lap_tim | `scripts/prepare-data.ts` | Download OpenF1's race snapshot and prepare SQLite | | `src/lib/f1-data.ts` | Source validation, importer, database, and driver catalog | | `src/lib/tools/lap-times.ts` | Function schema, argument validation, and read-only query | -| `src/library.ts` | Shared components for the prompt and renderer | +| `src/library.ts` | The chat library, shared by the prompt and renderer | | `src/lib/prompt.ts` | Gateway instructions and the supported data scope | | `src/lib/gateway-history.ts` | Loads a thread's stored turns from Gateway as chat messages | | `src/app/api/chat/route.ts` | `runTools()` generation, streaming, and turn storage | @@ -83,6 +83,6 @@ In the browser, inspect `query_lap_times` under **Behind the scenes**, watch par ## Adapt it -Replace `queryLapTimes` with a query to your database or API and update the function schema and prompt. Extend `src/library.ts` to support additional presentations, then regenerate the specification. +Replace `queryLapTimes` with a query to your database or API and update the function schema and prompt. Add your own components to `src/library.ts` for other presentations, then regenerate the specification. To run the tool and prompt on an agent framework instead, see the [LangGraph Platform](../../agent-frameworks/langgraph-platform), [Vercel AI SDK](../../agent-frameworks/vercel-ai-sdk), [Vercel Eve](../../agent-frameworks/vercel-eve), [Mastra](../../agent-frameworks/mastra), and [Google ADK](../../agent-frameworks/google-adk) examples. diff --git a/examples/cookbooks/conversational-analytics/src/lib/prompt.ts b/examples/cookbooks/conversational-analytics/src/lib/prompt.ts index c2c6b977d..e0df8ea86 100644 --- a/examples/cookbooks/conversational-analytics/src/lib/prompt.ts +++ b/examples/cookbooks/conversational-analytics/src/lib/prompt.ts @@ -3,9 +3,10 @@ import spec from "../generated/spec.json"; import { race, type Driver } from "./f1-data"; // Syntax guidance only. Real answers use values returned by query_lap_times. -const exampleProgram = `root = Stack([heading, comparison], "column", "l") -heading = TextContent("Example lap-time comparison", "large-heavy") -comparison = Card([CardHeader("Driver comparison", "Positive: Driver A faster; negative: Driver B faster. Illustrative values."), LineChart(["1", "2", "3"], [Series("Driver B minus Driver A", [0.9, 0.2, -0.2])], "linear", "Lap", "Lap time difference (seconds)")]) +const exampleProgram = `root = Card([header, chart, followUps]) +header = CardHeader("Driver comparison", "Positive: Driver A faster; negative: Driver B faster. Illustrative values.") +chart = LineChart(["1", "2", "3"], [Series("Driver B minus Driver A", [0.9, 0.2, -0.2])], "linear", "Lap", "Lap time difference (seconds)") +followUps = FollowUpBlock([FollowUpItem("Compare their final ten laps"), FollowUpItem("Add a third driver")]) `; export function analyticsPrompt(drivers: Driver[]) { diff --git a/examples/cookbooks/conversational-analytics/src/library.ts b/examples/cookbooks/conversational-analytics/src/library.ts index fdde462ff..0ece866c5 100644 --- a/examples/cookbooks/conversational-analytics/src/library.ts +++ b/examples/cookbooks/conversational-analytics/src/library.ts @@ -1,23 +1,2 @@ -import { createLibrary } from "@openuidev/react-lang"; -import { openuiChatLibrary, openuiLibrary } from "@openuidev/react-ui/genui-lib"; - -// The same subset generates the server's specification and renders in the client. -export const library = createLibrary({ - root: "Stack", - components: [ - ...[ - "Stack", - "Card", - "CardHeader", - "TextContent", - "LineChart", - "BarChart", - "Series", - "Table", - "Col", - ].map((name) => openuiLibrary.components[name]), - // Follow-up suggestions live in the chat library. - openuiChatLibrary.components.FollowUpBlock, - openuiChatLibrary.components.FollowUpItem, - ], -}); +// The chat library generates the server's specification and renders in the client. +export { openuiChatLibrary as library } from "@openuidev/react-ui/genui-lib"; diff --git a/examples/cookbooks/document-comparison/README.md b/examples/cookbooks/document-comparison/README.md index 8cfad0f8e..022d2b699 100644 --- a/examples/cookbooks/document-comparison/README.md +++ b/examples/cookbooks/document-comparison/README.md @@ -49,8 +49,7 @@ Try: | `src/lib/documents.ts` | Sample document sources, database schema, and document list | | `src/lib/embeddings.ts` | OpenAI embeddings and similarity | | `src/lib/tools/search-documents.ts` | Function schema, argument validation, and passage search | -| `src/library.ts` | Shared components for the prompt and renderer | -| `src/components/sources.tsx` | The Sources component, built on React UI's source strip | +| `src/library.ts` | The chat library, shared by the prompt and renderer | | `src/lib/prompt.ts` | Comparison rules and example answers for Gateway | | `src/lib/gateway-history.ts` | Loads a thread's stored turns from Gateway as chat messages | | `src/app/api/chat/route.ts` | `runTools()` generation, streaming, and turn storage | diff --git a/examples/cookbooks/document-comparison/src/components/sources.tsx b/examples/cookbooks/document-comparison/src/components/sources.tsx deleted file mode 100644 index 77e84c6e3..000000000 --- a/examples/cookbooks/document-comparison/src/components/sources.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { defineComponent } from "@openuidev/react-lang"; -import { - CardSourceProvider, - CardSourceSchema, - Sources as SourceStrip, - type CardSource, -} from "@openuidev/react-ui"; -import { z } from "zod/v4"; - -// One card per page: the strip keys cards by URL, and two criteria can share a page. -function onePerPage(items: CardSource[]) { - const pages = new Map(); - items.forEach((item, index) => { - const key = item.url || String(index); - const seen = pages.get(key); - pages.set( - key, - seen ? { ...seen, title: [seen.title, item.title].filter(Boolean).join(" ") } : item, - ); - }); - return [...pages.values()]; -} - -// Reuses the Sources strip from React UI: one card per quoted page, opening the report there. -export const Sources = defineComponent({ - name: "Sources", - props: z.object({ items: z.array(CardSourceSchema) }), - description: - "A strip of source cards. Each item is { title, sourceName, url }: title is the quote, sourceName names the document and page, and url opens the page.", - component: ({ props }) => ( - - - - ), -}); diff --git a/examples/cookbooks/document-comparison/src/lib/prompt.ts b/examples/cookbooks/document-comparison/src/lib/prompt.ts index 8cf577485..9908580b0 100644 --- a/examples/cookbooks/document-comparison/src/lib/prompt.ts +++ b/examples/cookbooks/document-comparison/src/lib/prompt.ts @@ -4,23 +4,23 @@ import type { Document } from "./documents"; // Syntax guidance only. Real answers use passages returned by search_documents. // One example compares a single period; the other shows a trend and a breakdown by segment. -const singlePeriodExample = `root = Stack([header, table, chart, gap, sources, followUps], "column", "l") +const singlePeriodExample = `root = Card([header, table, chart, gap, followUps], sources) header = CardHeader("Revenue, latest fiscal year", "Company A report · Company B report") table = Table([Col("Criterion", ["Revenue", "Revenue growth"]), Col("Company A", ["$10.0 billion (p. 12)", "Up 5% (p. 12)"]), Col("Company B", ["$8.0 billion (p. 40)", "Not found"])]) chart = HorizontalBarChart(["Company A", "Company B"], [Series("Revenue (USD billions)", [10.0, 8.0])], "grouped", "USD billions", "Company") gap = Callout("info", "Growth not reported", "Company B's report does not state revenue growth, so it is left out of the comparison.") -sources = Sources([{title: "“Revenue for the fiscal year was $10.0 billion, up 5% from a year ago.”", sourceName: "Company A · Page 12", url: "https://example.com/company-a.pdf#page=12"}, {title: "“Net revenue was $8.0 billion in 2025.”", sourceName: "Company B · Page 40", url: "https://example.com/company-b.pdf#page=40"}]) followUps = FollowUpBlock([FollowUpItem("Add operating income"), FollowUpItem("Break down revenue by segment")]) +sources = [{title: "“Revenue for the fiscal year was $10.0 billion, up 5% from a year ago.”", sourceName: "Company A · Page 12", url: "https://example.com/company-a.pdf#page=12"}, {title: "“Net revenue was $8.0 billion in 2025.”", sourceName: "Company B · Page 40", url: "https://example.com/company-b.pdf#page=40"}] `; -const trendExample = `root = Stack([header, trend, breakdown, parts, note, sources, followUps], "column", "l") +const trendExample = `root = Card([header, trend, breakdown, parts, note, followUps], sources) header = CardHeader("Revenue trend and segments", "Company A report · Company B report") trend = LineChart(["2023", "2024", "2025"], [Series("Company A", [8.1, 9.5, 10.0]), Series("Company B", [7.2, 7.9, 8.0])], "linear", "Fiscal year", "Revenue (USD billions)") breakdown = HorizontalBarChart(["Company A · Data center", "Company A · Client", "Company B · Client", "Company B · Data center", "Company B · Embedded"], [Series("Revenue (USD billions)", [6.0, 4.0, 4.1, 3.1, 0.8])], "grouped", "Revenue (USD billions)", "Segment") parts = Table([Col("Company", ["Company A", "Company A", "Company B", "Company B", "Company B"]), Col("Segment", ["Data center", "Client", "Client", "Data center", "Embedded"]), Col("Revenue", ["$6.0 billion (p. 47)", "$4.0 billion (p. 47)", "$4.1 billion (p. 55)", "$3.1 billion (p. 55)", "$0.8 billion (p. 55)"])]) note = Callout("warning", "Segments differ", "Each company defines its own segments, so compare totals and shares rather than segment names.") -sources = Sources([{title: "“Revenue was $10.0 billion, $9.5 billion, and $8.1 billion in fiscal 2025, 2024, and 2023.”", sourceName: "Company A · Page 45", url: "https://example.com/company-a.pdf#page=45"}, {title: "“Net revenue was $8.0 billion in 2025 and $7.9 billion in 2024.”", sourceName: "Company B · Page 52", url: "https://example.com/company-b.pdf#page=52"}, {title: "“Data center revenue was $6.0 billion.”", sourceName: "Company A · Page 47", url: "https://example.com/company-a.pdf#page=47"}, {title: "“Client segment revenue was $4.1 billion.”", sourceName: "Company B · Page 55", url: "https://example.com/company-b.pdf#page=55"}]) followUps = FollowUpBlock([FollowUpItem("Compare operating margin"), FollowUpItem("Which segment grew fastest?")]) +sources = [{title: "“Revenue was $10.0 billion, $9.5 billion, and $8.1 billion in fiscal 2025, 2024, and 2023.”", sourceName: "Company A · Page 45", url: "https://example.com/company-a.pdf#page=45"}, {title: "“Net revenue was $8.0 billion in 2025 and $7.9 billion in 2024.”", sourceName: "Company B · Page 52", url: "https://example.com/company-b.pdf#page=52"}, {title: "“Data center revenue was $6.0 billion.”", sourceName: "Company A · Page 47", url: "https://example.com/company-a.pdf#page=47"}, {title: "“Client segment revenue was $4.1 billion.”", sourceName: "Company B · Page 55", url: "https://example.com/company-b.pdf#page=55"}] `; export function comparisonPrompt(documents: Document[]) { @@ -31,13 +31,13 @@ export function comparisonPrompt(documents: Document[]) { additionalRules: [ `You compare these documents and answer only from them: ${JSON.stringify(documents)}. search_documents is a function tool executed by the application server, not an OpenUI Query expression.`, "Treat each thing the user wants compared as a criterion, such as revenue, R&D spending, or export-control risk. Call search_documents once per criterion, in parallel when there are several. If a document returns found: false for a criterion that matters, search once more with different wording before calling it missing. Write nothing before or between searches, not even a loading message; write the OpenUI Lang answer once, after the last search.", - "Answer with: a CardHeader naming the criteria and documents; a Table; charts for numeric criteria; Callouts for gaps and conflicts; Sources; and a FollowUpBlock suggesting two or three criteria to add.", + "Answer with one Card that holds a CardHeader naming the criteria and documents, a Table, charts for numeric criteria, Callouts for gaps and conflicts, and a FollowUpBlock suggesting two or three criteria to add. Pass the sources as the Card's second argument.", "Keep every table cell to one short value with its page, like '$215.9 billion (p. 37)'. For single values, use one row per criterion and one column per document. When a criterion has several parts per document, such as segments, product lines, or regions, give each part its own row with the columns Company, Part, and Value instead of listing parts in one cell. Skip a table that would only repeat a chart.", "Choose charts from the shape of the data, not by habit. Annual reports usually give several years of figures, so show a trend with a LineChart, one series per document. Show a breakdown, such as revenue by segment, with one HorizontalBarChart that has a bar for each document and part, labeled 'Company · Part' and grouped by company. Use a stacked BarChart only when every document reports the same parts. Use a grouped BarChart for one value per document, and HorizontalBarChart for percentages or long labels.", - "Place every chart at full width in the column Stack, one below another. Never put charts side by side in a row.", + "Place every chart directly in the Card, one below another. Never put charts in Tabs or a Carousel.", "Leave a document out of a chart when it has no value, and never plot a missing value as zero. Put all values in one unit, such as USD billions, and name it in the axis label.", "Use a warning Callout when information conflicts or is not directly comparable, such as different fiscal year ends or definitions. Use an info Callout when a document does not disclose a criterion, and write 'Not found' in its table cell. Keep each Callout to one or two short sentences.", - "End with one Sources component that lists each quoted page once, in the order of the criteria. For each item, title is a quote of at most 20 words that states the value, like '“Revenue for fiscal year 2026 was $215.9 billion, up 65%.”'; sourceName is the company and page, like 'NVIDIA · Page 37'; and url is the passage's url from search_documents, left out when the passage has none. When the value comes from a table, write its row label and values instead of a quote, like 'Net revenue (USD millions): 52,853 in 2025, 53,101 in 2024'. Use only facts stated in the returned passages.", + "List each quoted page once in the Card's sources, in the order of the criteria; when one page supports several values, give it a single item. For each item, title is a quote of at most 20 words that states the value, like '“Revenue for fiscal year 2026 was $215.9 billion, up 65%.”'; sourceName is the company and page, like 'NVIDIA · Page 37'; and url is the passage's url from search_documents, left out when the passage has none. When the value comes from a table, write its row label and values instead of a quote, like 'Net revenue (USD millions): 52,853 in 2025, 53,101 in 2024'. Use only facts stated in the returned passages.", "On follow-ups, keep the documents and earlier criteria in mind. When the user adds a criterion, search for it and show the comparison for that criterion. Do not generate Query, Mutation, reactive variables, or form controls.", ], examples: [singlePeriodExample, trendExample], diff --git a/examples/cookbooks/document-comparison/src/library.ts b/examples/cookbooks/document-comparison/src/library.ts index a069383de..0ece866c5 100644 --- a/examples/cookbooks/document-comparison/src/library.ts +++ b/examples/cookbooks/document-comparison/src/library.ts @@ -1,27 +1,2 @@ -import { createLibrary } from "@openuidev/react-lang"; -import { openuiChatLibrary, openuiLibrary } from "@openuidev/react-ui/genui-lib"; -import { Sources } from "./components/sources"; - -// The same subset generates the server's specification and renders in the client. -export const library = createLibrary({ - root: "Stack", - components: [ - ...[ - "Stack", - "CardHeader", - "TextContent", - "Table", - "Col", - "BarChart", - "HorizontalBarChart", - "LineChart", - "Series", - "Callout", - "TagBlock", - ].map((name) => openuiLibrary.components[name]), - Sources, - // Follow-up suggestions live in the chat library. - openuiChatLibrary.components.FollowUpBlock, - openuiChatLibrary.components.FollowUpItem, - ], -}); +// The chat library generates the server's specification and renders in the client. +export { openuiChatLibrary as library } from "@openuidev/react-ui/genui-lib"; From 5fb9fe5ea79ea3453a545961ea9087e2cbca2d7e Mon Sep 17 00:00:00 2001 From: Visharad Kashyap <154831195+vishxrad@users.noreply.github.com> Date: Wed, 30 Sep 2026 12:18:55 +0530 Subject: [PATCH 2/3] docs(cookbooks): update the analytics references for the chat library The example now exports React UI's chat library instead of building one with createLibrary(), so drop the @openuidev/react-lang reference and name the chat library under @openuidev/react-ui. Co-Authored-By: Claude Opus 5.5 --- docs/content/docs/cookbooks/conversational-analytics.mdx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/content/docs/cookbooks/conversational-analytics.mdx b/docs/content/docs/cookbooks/conversational-analytics.mdx index 8e2e0c38d..1ce9b4d4a 100644 --- a/docs/content/docs/cookbooks/conversational-analytics.mdx +++ b/docs/content/docs/cookbooks/conversational-analytics.mdx @@ -254,9 +254,8 @@ This example runs locally without authentication. Before deploying it, add [auth ## References -- [`@openuidev/react-ui`](/docs/api-reference/react-ui): `AgentInterface` and the built-in component libraries. +- [`@openuidev/react-ui`](/docs/api-reference/react-ui): `AgentInterface` and the chat library. - [`@openuidev/react-headless`](/docs/api-reference/react-headless): `fetchLLM()`, `openAIReadableStreamAdapter()`, and `useOpenuiCloudStorage()`, which the example imports through `@openuidev/react-ui`. -- [`@openuidev/react-lang`](/docs/api-reference/react-lang): `createLibrary()`. - [`@openuidev/lang-core`](/docs/openui-lang/system-prompts): `generateSystemPrompt()`. - [`@openuidev/server`](/docs/api-reference/server#conversation-history): `storeChatCompletionHistory()`. - [`@openuidev/cli`](/docs/api-reference/cli): `openui generate`. From 6f2fa8ded41da1f2f4c8d6857e7b235654576cf2 Mon Sep 17 00:00:00 2001 From: Visharad Kashyap <154831195+vishxrad@users.noreply.github.com> Date: Wed, 30 Sep 2026 12:53:15 +0530 Subject: [PATCH 3/3] docs(cookbooks): update the comparison and booking references for the chat library Document comparison now exports React UI's chat library, so drop its @openuidev/react-lang reference. Both pages name the chat library under @openuidev/react-ui; booking still uses react-lang for createLibrary() and its DatePicker. Co-Authored-By: Claude Opus 5.5 --- docs/content/docs/cookbooks/booking-assistant.mdx | 2 +- docs/content/docs/cookbooks/document-comparison.mdx | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/content/docs/cookbooks/booking-assistant.mdx b/docs/content/docs/cookbooks/booking-assistant.mdx index 301605aa1..1fa81d3eb 100644 --- a/docs/content/docs/cookbooks/booking-assistant.mdx +++ b/docs/content/docs/cookbooks/booking-assistant.mdx @@ -290,7 +290,7 @@ This example runs locally without authentication. Before deploying it, add [auth ## References -- [`@openuidev/react-ui`](/docs/api-reference/react-ui): `AgentInterface` and the built-in component libraries. +- [`@openuidev/react-ui`](/docs/api-reference/react-ui): `AgentInterface` and the chat library. - [`@openuidev/react-headless`](/docs/api-reference/react-headless): `fetchLLM()`, `openAIReadableStreamAdapter()`, and `useOpenuiCloudStorage()`, which the example imports through `@openuidev/react-ui`. - [`@openuidev/react-lang`](/docs/api-reference/react-lang): `createLibrary()` and `defineComponent()`. - [`@openuidev/lang-core`](/docs/openui-lang/system-prompts): `generateSystemPrompt()`. diff --git a/docs/content/docs/cookbooks/document-comparison.mdx b/docs/content/docs/cookbooks/document-comparison.mdx index 2daa34fb8..a8fb6254b 100644 --- a/docs/content/docs/cookbooks/document-comparison.mdx +++ b/docs/content/docs/cookbooks/document-comparison.mdx @@ -340,9 +340,8 @@ This example runs locally without authentication. Before deploying it, add [auth ## References -- [`@openuidev/react-ui`](/docs/api-reference/react-ui): `AgentInterface` and the built-in component libraries. +- [`@openuidev/react-ui`](/docs/api-reference/react-ui): `AgentInterface` and the chat library. - [`@openuidev/react-headless`](/docs/api-reference/react-headless): `fetchLLM()`, `openAIReadableStreamAdapter()`, and `useOpenuiCloudStorage()`, which the example imports through `@openuidev/react-ui`. -- [`@openuidev/react-lang`](/docs/api-reference/react-lang): `createLibrary()` and `defineComponent()`. - [`@openuidev/lang-core`](/docs/openui-lang/system-prompts): `generateSystemPrompt()`. - [`@openuidev/server`](/docs/api-reference/server#conversation-history): `storeChatCompletionHistory()`. - [`@openuidev/cli`](/docs/api-reference/cli): `openui generate`.