diff --git a/docs/app/docs/chat/page.tsx b/docs/app/docs/chat/page.tsx deleted file mode 100644 index 86243152e..000000000 --- a/docs/app/docs/chat/page.tsx +++ /dev/null @@ -1,193 +0,0 @@ -import { Button } from "@/components/button"; -import { - CodeBlock, - FeatureCard, - FeatureCards, - Separator, - SimpleCard, -} from "@/components/overview-components"; -import { - Code2, - Database, - Layout, - Maximize2, - MessageCircle, - MessageSquare, - PanelRightOpen, - Zap, - type LucideIcon, -} from "lucide-react"; -import Link from "next/link"; - -export const metadata = { - title: "OpenUI Chat SDK", - description: - "Production-ready chat UI for AI agents. Drop-in layouts, streaming, and state management.", -}; - -const headlessCode = `import { useChat } from '@openuidev/react'; - -function CustomChat() { - const { messages, append, isLoading } = useChat(); - - return ( -
- {messages.map(m => ( -
- {m.content} -
- ))} - - append(e.target.value)} - /> -
- ); -}`; - -const layoutOptions = [ - { - icon: , - title: "Copilot", - description: "A sidebar assistant that lives alongside your main application content.", - href: "/docs/chat/copilot", - }, - { - icon: , - title: "Full Screen", - description: "A standalone, immersive chat page similar to ChatGPT or Claude.", - href: "/docs/chat/fullscreen", - }, - { - icon: , - title: "Bottom Tray", - description: "A floating support-style widget that expands from the bottom corner.", - href: "/docs/chat/bottom-tray", - }, -] as const; - -const capabilities = [ - { - icon: , - title: "Streaming Native", - description: "Handles text deltas, optimistic updates, loading states, and partial responses.", - }, - { - icon: , - title: "Thread Persistence", - description: "Save and restore conversation history with straightforward API contracts.", - }, - { - icon: , - title: "Composable State", - description: "Use the same primitives across prebuilt layouts and fully custom chat surfaces.", - }, -] as const; - -function SectionHeader({ - icon: Icon, - title, - description, -}: { - icon: LucideIcon; - title: string; - description: string; -}) { - return ( -
-
- -
-
-

{title}

-

{description}

-
-
- ); -} - -export default function ChatOverviewPage() { - return ( -
-
-

OpenUI Chat SDK

-

- Production-ready chat UI for AI agents. Start with prebuilt layouts for fast integration, - then drop down to headless hooks when you need full control over behavior and rendering. -

-
-
-
- - - -
- - - - {layoutOptions.map((item) => ( - - ))} - -
- - - -
-
- - - - {capabilities.map((item) => ( - - ))} - -
- -
- - - -

- The `useChat` hook gives you message state, append helpers, and loading semantics - without locking you into a specific UI. -

- - - Read the Headless Guide - -
-
-
-
- ); -} diff --git a/docs/components/docs-navbar.tsx b/docs/components/docs-navbar.tsx index c0efddf28..59ca8731e 100644 --- a/docs/components/docs-navbar.tsx +++ b/docs/components/docs-navbar.tsx @@ -12,16 +12,19 @@ import styles from "./docs-navbar.module.css"; import { SiteHeaderFrame } from "./site-header"; import { ThemeToggle } from "./theme-toggle"; -const tabs = [ +const tabs: { title: string; url: string; match?: string }[] = [ { title: "OpenUI", url: "/docs/openui-lang" }, - { title: "Chat", url: "/docs/chat" }, + { title: "Agent Interface", url: "/docs/agent/getting-started/introduction", match: "/docs/agent" }, { title: "API Reference", url: "/docs/api-reference" }, -] as const; +]; function activeTabUrl(pathname: string): string { - const sorted = [...tabs].sort((a, b) => b.url.length - a.url.length); + const sorted = [...tabs].sort((a, b) => (b.match ?? b.url).length - (a.match ?? a.url).length); return ( - sorted.find((t) => pathname === t.url || pathname.startsWith(`${t.url}/`))?.url ?? tabs[0].url + sorted.find((t) => { + const prefix = t.match ?? t.url; + return pathname === prefix || pathname.startsWith(`${prefix}/`); + })?.url ?? tabs[0].url ); } diff --git a/docs/components/overview-components/chat-modal.css b/docs/components/overview-components/chat-modal.css index c98c21cfd..5fbd3cbbe 100644 --- a/docs/components/overview-components/chat-modal.css +++ b/docs/components/overview-components/chat-modal.css @@ -69,14 +69,14 @@ height: 100%; } -/* Override Shell container sizing to fit within the modal */ -.chat-modal-body .openui-shell-container { +/* Override AgentInterface container sizing to fit within the modal */ +.chat-modal-body .openui-agent-container { height: 100% !important; width: 100% !important; } /* Hide the sidebar in the modal */ -.chat-modal-body .openui-shell-sidebar-container { +.chat-modal-body .openui-agent-sidebar-container { display: none !important; } diff --git a/docs/components/overview-components/chat-modal.tsx b/docs/components/overview-components/chat-modal.tsx index 29934194f..f45cffbfb 100644 --- a/docs/components/overview-components/chat-modal.tsx +++ b/docs/components/overview-components/chat-modal.tsx @@ -8,12 +8,16 @@ import "./chat-modal.css"; import { DemoCreditsDialog } from "@/components/DemoCreditsDialog"; import { isDemoCreditsErrorPayload } from "@/lib/demo-credits"; -import { openAIAdapter, openAIMessageFormat } from "@openuidev/react-headless"; -import { FullScreen } from "@openuidev/react-ui"; +import { + AgentInterface, + openAIAdapter, + openAIMessageFormat, + type ChatLLM, +} from "@openuidev/react-ui"; import { openuiChatLibrary } from "@openuidev/react-ui/genui-lib"; import { X } from "lucide-react"; import { useTheme } from "next-themes"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { createPortal } from "react-dom"; interface ChatModalProps { @@ -40,6 +44,41 @@ export function ChatModal({ onClose }: ChatModalProps) { }; }, [handleKey]); + // The backend call — including the demo-credits error handling — is unchanged; + // only the chat surface moved from FullScreen to AgentInterface. AgentInterface + // uses its built-in in-memory thread storage (wiped on reload). + const llm = useMemo( + () => ({ + send: async ({ messages, signal }) => { + const response = await fetch("/api/chat", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + messages: openAIMessageFormat.toApi(messages), + }), + signal, + }); + + if (!response.ok) { + const err = await response + .clone() + .json() + .catch(() => ({})); + if (isDemoCreditsErrorPayload((err as { error?: unknown }).error)) { + setShowOverviewCreditsDialog(true); + return new Response("data: [DONE]\n\n", { + headers: { "Content-Type": "text/event-stream" }, + }); + } + } + + return response; + }, + streamProtocol: openAIAdapter(), + }), + [], + ); + return createPortal(
e.stopPropagation()}> @@ -47,63 +86,37 @@ export function ChatModal({ onClose }: ChatModalProps) {
- { - const response = await fetch("/api/chat", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - messages: openAIMessageFormat.toApi(messages), - }), - signal: abortController.signal, - }); - - if (!response.ok) { - const err = await response - .clone() - .json() - .catch(() => ({})); - if (isDemoCreditsErrorPayload((err as { error?: unknown }).error)) { - setShowOverviewCreditsDialog(true); - return new Response("data: [DONE]\n\n", { - headers: { "Content-Type": "text/event-stream" }, - }); - } - } - - return response; - }} - streamProtocol={openAIAdapter()} + + starterVariant="short" + starters={[ + { + displayText: "Revenue dashboard", + prompt: + "Build a revenue dashboard with a bar chart showing monthly revenue for Q4, key metrics, and a summary table.", + }, + { + displayText: "Signup form", + prompt: + "Create a user registration form with name, email, password, and country fields with validation.", + }, + { + displayText: "Compare React vs Vue", + prompt: + "Show me a comparison of React and Vue frameworks using tabs with pros, cons, and a feature comparison table.", + }, + { + displayText: "Travel destinations", + prompt: + "Show me a carousel of 3 popular travel destinations with images, descriptions, and best time to visit.", + }, + ]} + > + +
@@ -199,25 +198,26 @@ export function AssistantMessage({ content, isStreaming }) {

- Pre-built chat layouts (Copilot, Fullscreen, Bottom Tray) or build custom UIs with - headless hooks. Fully themeable and accessible out of the box. + Drop in AgentInterface — a production-ready artifact chat surface with thread + history, conversation starters, and streaming — or build custom UIs with headless hooks. + Fully themeable and accessible out of the box.

@@ -254,25 +254,31 @@ export function AssistantMessage({ content, isStreaming }) { + fetch("/api/chat", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ messages: openAIMessageFormat.toApi(messages) }), + signal, + }), + streamProtocol: openAIAdapter(), +}; + + `} /> - -
-
@@ -349,12 +355,28 @@ const customLibrary = createLibrary({ + fetch('/api/chat', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ messages: openAIMessageFormat.toApi(messages) }), + signal, + }), + streamProtocol: openAIAdapter(), +}; function App() { return ( - ); diff --git a/docs/content/docs/agent/core-concepts/artifacts.mdx b/docs/content/docs/agent/core-concepts/artifacts.mdx new file mode 100644 index 000000000..c9ec10d37 --- /dev/null +++ b/docs/content/docs/agent/core-concepts/artifacts.mdx @@ -0,0 +1,44 @@ +--- +title: Artifacts +description: Durable, first-class conversation outputs like slides, reports, and apps that the user can open and return to. +--- + +An **artifact** is a first-class output of a conversation. Slides, reports, dashboards, a small app: things the user opens, reads, and returns to. An artifact is not a chat message and not a tool result. Once it exists, it stands on its own. + +On OpenUI Cloud, the agent produces **slides and reports** for you, with nothing to wire. + +## Where an artifact shows up + +The same renderer drives two surfaces: + +- **Preview:** a compact, inline view shown inside the chat message as the artifact is produced. +- **Actual:** the full view, opened in a panel or on its own page. + +