Compare commits

...

24 Commits

Author SHA1 Message Date
Logan a514498830 Merge pull request #200 from run-llama/vercel/react-server-components-cve-vu-grfhrf 2025-12-16 16:42:29 -06:00
Vercel b27fae84a5 Fix React Server Components CVE vulnerabilities
Updated dependencies to fix Next.js and React CVE vulnerabilities.

The fix-react2shell-next tool automatically updated the following packages to their secure versions:
- next
- react-server-dom-webpack
- react-server-dom-parcel  
- react-server-dom-turbopack

All package.json files have been scanned and vulnerable versions have been patched to the correct fixed versions based on the official React advisory.

Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
2025-12-16 22:38:26 +00:00
github-actions[bot] 7d36f0f2f8 Release (#195)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-28 09:17:10 +07:00
Thuc Pham 8fd4f4b3ec feat: add custom markdown example (#194) 2025-08-28 09:10:16 +07:00
Pat Newell b092d5ef4e Expose ReactMarkdown components prop (#193)
* Expose ReactMarkdown `components` prop

* Expose ReactMarkdown components prop

---------

Co-authored-by: Thuc Pham <51660321+thucpn@users.noreply.github.com>
2025-08-28 09:01:09 +07:00
thucpn e09ac1cb06 fix: get user input in nextjs example 2025-08-13 11:37:54 +07:00
github-actions[bot] 10044f38b5 Release (#191)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-13 11:46:41 +08:00
Marcus Schiesser 8cad367ae3 chore: make everything minor 2025-08-13 11:35:14 +08:00
Thuc Pham 1ceb4ba041 feat: support Vercel AI SDK 5 (#189) 2025-08-13 11:23:02 +08:00
Thuc Pham ee1a489dd5 publishConfig access public for server package 2025-07-21 09:40:34 +00:00
github-actions[bot] 5a9744c0c3 Release (#187)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-21 16:18:26 +07:00
Thuc Pham 682e836bdb fix: eject make changeset release fail (#186)
* fix: eject make changeset release fail

* Create cyan-jokes-hang.md
2025-07-21 16:10:16 +07:00
github-actions[bot] 27d6c922fc Release (#185)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-21 15:41:28 +07:00
thucpn fa6e8510d6 fix: changeset issue 2025-07-21 15:33:51 +07:00
github-actions[bot] 75a8a5190e Release (#184)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-21 15:28:23 +07:00
Thuc Pham f726f19a33 fix: missing document for ChatSection (#183)
* fix: missing document

* Create tough-parents-judge.md
2025-07-21 15:19:39 +07:00
Thuc Pham e8a20643d1 update doc for custom open canvas (#182) 2025-07-21 15:10:51 +07:00
thucpn 6196621cd6 fix changeset release error 2025-07-21 15:01:15 +07:00
github-actions[bot] 3acf2cf25c Release (#181)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-21 14:48:21 +07:00
Thuc Pham 82bc236338 feat: support config auto open canvas (#180)
* feat: support config auto open canvas

* Create rude-games-yell.md

* defaultTab
2025-07-21 14:41:22 +07:00
Thuc Pham 4aad967255 chore: move @llamaindex/server to chat-ui repo (#176) 2025-07-16 09:13:23 +08:00
Huu Le bfdd3c012d add uv lock file and improve readme (#174) 2025-07-11 15:45:17 +08:00
Marcus Schiesser 685b79aee6 chore: update llamadeploy chat examples (structure and readme) 2025-07-11 14:37:17 +08:00
Thuc Pham c3e62265ca fix: remove workspace packages from examples (#173)
* fix: remove workspace packages from examples

* update fe for python example

* exclude examples from pnpm workspace
2025-07-11 11:47:03 +07:00
325 changed files with 28858 additions and 5128 deletions
+3 -3
View File
@@ -83,7 +83,7 @@ Components are designed to be composable. You can use them as is:
```tsx
import { ChatSection } from '@llamaindex/chat-ui'
import { useChat } from 'ai/react'
import { useChat } from '@ai-sdk/react'
const ChatExample = () => {
const handler = useChat()
@@ -96,7 +96,7 @@ Or you can extend them with your own children components:
```tsx
import { ChatSection, ChatMessages, ChatInput } from '@llamaindex/chat-ui'
import LlamaCloudSelector from './components/LlamaCloudSelector' // your custom component
import { useChat } from 'ai/react'
import { useChat } from '@ai-sdk/react'
const ChatExample = () => {
const handler = useChat()
@@ -158,7 +158,7 @@ Additionally, you can also override each component's styles by setting custom cl
```tsx
import { ChatSection, ChatMessages, ChatInput } from '@llamaindex/chat-ui'
import { useChat } from 'ai/react'
import { useChat } from '@ai-sdk/react'
const ChatExample = () => {
const handler = useChat()
+28
View File
@@ -1,5 +1,33 @@
# web
## 1.1.1
### Patch Changes
- Updated dependencies [b092d5e]
- @llamaindex/chat-ui@0.6.1
- @llamaindex/dynamic-ui@1.0.1
## 1.1.0
### Minor Changes
- 1ceb4ba: support vercel ai sdk ver 5
### Patch Changes
- Updated dependencies [1ceb4ba]
- @llamaindex/chat-ui@0.6.0
- @llamaindex/dynamic-ui@1.0.0
## 1.0.50
### Patch Changes
- Updated dependencies [82bc236]
- @llamaindex/chat-ui@0.5.17
- @llamaindex/dynamic-ui@0.0.4
## 1.0.49
### Patch Changes
+43 -14
View File
@@ -1,13 +1,13 @@
import { Message, LlamaIndexAdapter, StreamData } from 'ai'
import { fakeStreamText, TextChunk, writeStream } from '@/app/utils'
import { UIMessage as Message } from '@ai-sdk/react'
import {
ChatMessage,
MessageContentDetail,
OpenAI,
OpenAIEmbedding,
Settings,
SimpleChatEngine,
} from 'llamaindex'
import { NextResponse, type NextRequest } from 'next/server'
import { fakeStreamText } from '@/app/utils'
export const runtime = 'nodejs'
export const dynamic = 'force-dynamic'
@@ -24,13 +24,11 @@ export async function POST(request: NextRequest) {
const messages = body.messages
const lastMessage = messages[messages.length - 1]
const vercelStreamData = new StreamData()
if (!process.env.OPENAI_API_KEY) {
// Return fake stream if API key is not set
return new Response(fakeStreamText(), {
headers: {
'Content-Type': 'text/plain',
'Content-Type': 'text/event-stream',
Connection: 'keep-alive',
},
})
@@ -38,18 +36,49 @@ export async function POST(request: NextRequest) {
const chatEngine = new SimpleChatEngine()
const messageContent = (lastMessage.parts[0] as { text: string }).text
const response = await chatEngine.chat({
message: lastMessage.content,
chatHistory: messages as ChatMessage[],
message: messageContent,
chatHistory: messages.map(message => ({
role: message.role,
content: message.parts as MessageContentDetail[],
})),
stream: true,
})
return LlamaIndexAdapter.toDataStreamResponse(response, {
data: vercelStreamData,
callbacks: {
onCompletion: async () => {
await vercelStreamData.close()
},
const sseStream = new ReadableStream({
async start(controller) {
// Generate a unique message id
const messageId = crypto.randomUUID()
// Start the text chunk
const startChunk: TextChunk = { id: messageId, type: 'text-start' }
writeStream(controller, startChunk)
// Consume the response and write the chunks to the controller
for await (const chunk of response) {
writeStream(controller, {
id: messageId,
type: 'text-delta',
delta: chunk.delta,
})
}
// End the text chunk
const endChunk: TextChunk = { id: messageId, type: 'text-end' }
writeStream(controller, endChunk)
controller.close()
},
})
return new Response(sseStream, {
status: 200,
statusText: 'OK',
headers: {
'content-type': 'text/event-stream',
connection: 'keep-alive',
},
})
} catch (error) {
+21 -5
View File
@@ -9,19 +9,35 @@ import {
useChatCanvas,
} from '@llamaindex/chat-ui'
import { DynamicComponent } from '@llamaindex/dynamic-ui'
import { Message, useChat } from 'ai/react'
import { UIMessage as Message, useChat } from '@ai-sdk/react'
const initialMessages: Message[] = [
{
id: 'code-gen1',
role: 'user',
content: 'Generate a simple calculator',
parts: [{ type: 'text', text: 'Generate a simple calculator' }],
},
{
id: 'code-gen2',
role: 'assistant',
content:
'\n```annotation\n{"type":"artifact","data":{"type":"code","created_at":1752124365106,"data":{"language":"typescript","file_name":"calculator.tsx","code":"import React, { useState } from \\"react\\"\\nimport { Button } from \\"@/components/ui/button\\"\\nimport { Card } from \\"@/components/ui/card\\"\\nimport { cn } from \\"@/lib/utils\\"\\n\\nconst buttons = [\\n [\\"7\\", \\"8\\", \\"9\\", \\"/\\"],\\n [\\"4\\", \\"5\\", \\"6\\", \\"*\\"],\\n [\\"1\\", \\"2\\", \\"3\\", \\"-\\"],\\n [\\"0\\", \\"C\\", \\"=\\", \\"+\\"],\\n]\\n\\nexport default function Calculator() {\\n const [input, setInput] = useState<string>(\\"\\")\\n const [result, setResult] = useState<string | null>(null)\\n\\n const handleButtonClick = (value: string) => {\\n if (value === \\"C\\") {\\n setInput(\\"\\")\\n setResult(null)\\n return\\n }\\n if (value === \\"=\\") {\\n try {\\n // eslint-disable-next-line no-eval\\n const evalResult = eval(input)\\n setResult(evalResult.toString())\\n } catch {\\n setResult(\\"Error\\")\\n }\\n return\\n }\\n if (result !== null) {\\n setInput(value.match(/[0-9.]/) ? value : result + value)\\n setResult(null)\\n } else {\\n setInput((prev) => prev + value)\\n }\\n }\\n\\n return (\\n <div className=\\"flex items-center justify-center min-h-screen bg-muted\\">\\n <Card className=\\"w-[320px] p-6 shadow-lg\\">\\n <div className={cn(\\"mb-4 h-16 bg-background rounded flex items-end justify-end px-4 text-2xl font-mono border\\", result && \\"text-muted-foreground\\")}>\\n {result !== null ? result : input || \\"0\\"}\\n </div>\\n <div className=\\"grid grid-cols-4 gap-3\\">\\n {buttons.flat().map((btn, idx) => (\\n <Button\\n key={idx}\\n variant={btn === \\"C\\" ? \\"destructive\\" : btn === \\"=\\" ? \\"default\\" : \\"outline\\"}\\n className={cn(\\n \\"h-12 text-xl\\",\\n btn === \\"=\\" && \\"col-span-1 bg-primary text-primary-foreground\\",\\n btn === \\"C\\" && \\"col-span-1\\"\\n )}\\n onClick={() => handleButtonClick(btn)}\\n >\\n {btn}\\n </Button>\\n ))}\\n </div>\\n </Card>\\n </div>\\n )\\n}"}}}\n```\nHere\'s how the simple calculator works:\n\n- The calculator displays the current input or the result at the top.\n- You can click the number buttons (0-9) and the operators (+, -, *, /) to build your calculation.\n- Pressing the = button evaluates the expression and shows the result.\n- Pressing the C button clears the input and resets the calculator.\n- If you get an error (like dividing by zero or entering an invalid expression), "Error" will be displayed.\n\nYou can further customize the calculator\'s appearance or add more features as needed! If you have any questions about how the code works or want to add more functionality, let me know!',
parts: [
{
type: 'text',
text: "Here's the simple calculator:",
},
{
type: 'data-artifact',
data: {
type: 'code',
created_at: 1752124365106,
data: {
language: 'typescript',
file_name: 'calculator.tsx',
code: 'import React, { useState } from "react"\nimport { Button } from "@/components/ui/button"\nimport { Card } from "@/components/ui/card"\nimport { cn } from "@/lib/utils"\n\nconst buttons = [\n ["7", "8", "9", "/"],\n ["4", "5", "6", "*"],\n ["1", "2", "3", "-"],\n ["0", "C", "=", "+"],\n]\n\nexport default function Calculator() {\n const [input, setInput] = useState<string>("")\n const [result, setResult] = useState<string | null>(null)\n\n const handleButtonClick = (value: string) => {\n if (value === "C") {\n setInput("")\n setResult(null)\n return\n }\n if (value === "=") {\n try {\n // eslint-disable-next-line no-eval\n const evalResult = eval(input)\n setResult(evalResult.toString())\n } catch {\n setResult("Error")\n }\n return\n }\n if (result !== null) {\n setInput(value.match(/[0-9.]/) ? value : result + value)\n setResult(null)\n } else {\n setInput((prev) => prev + value)\n }\n }\n\n return (\n <div className="flex items-center justify-center min-h-screen bg-muted">\n <Card className="w-[320px] p-6 shadow-lg">\n <div className={cn("mb-4 h-16 bg-background rounded flex items-end justify-end px-4 text-2xl font-mono border", result && "text-muted-foreground")}>\n {result !== null ? result : input || "0"}\n </div>\n <div className="grid grid-cols-4 gap-3">\n {buttons.flat().map((btn, idx) => (\n <Button\n key={idx}\n variant={btn === "C" ? "destructive" : btn === "=" ? "default" : "outline"}\n className={cn(\n "h-12 text-xl",\n btn === "=" && "col-span-1 bg-primary text-primary-foreground",\n btn === "C" && "col-span-1"\n )}\n onClick={() => handleButtonClick(btn)}\n >\n {btn}\n </Button>\n ))}\n </div>\n </Card>\n </div>\n )\n}',
},
},
},
],
},
]
@@ -30,7 +46,7 @@ export default function Page(): JSX.Element {
}
function CustomChat() {
const handler = useChat({ initialMessages })
const handler = useChat({ messages: initialMessages })
return (
<ChatSection
+28 -50
View File
@@ -11,7 +11,7 @@ import {
useChatCanvas,
useChatUI,
} from '@llamaindex/chat-ui'
import { Message, useChat } from 'ai/react'
import { UIMessage as Message, useChat } from '@ai-sdk/react'
import { Image } from 'lucide-react'
const code = `
@@ -25,11 +25,11 @@ import {
useChatCanvas,
useChatUI,
} from '@llamaindex/chat-ui'
import { useChat } from 'ai/react'
import { useChat } from '@ai-sdk/react'
import { Image } from 'lucide-react'
export function CustomChat() {
const handler = useChat({ initialMessages: [] })
const handler = useChat()
return (
<ChatSection
@@ -103,11 +103,8 @@ function CustomChatMessages() {
>
<ChatMessage.Avatar />
<ChatMessage.Content>
<ChatMessage.Content.Markdown
annotationRenderers={{
artifact: CustomArtifactCard,
}}
/>
<ChatMessage.Part.Markdown />
<ChatMessage.Part.Artifact />
</ChatMessage.Content>
<ChatMessage.Actions />
</ChatMessage>
@@ -116,32 +113,24 @@ function CustomChatMessages() {
</ChatMessages>
)
}
// custom artifact card for image artifacts
function CustomArtifactCard({ data }: { data: Artifact }) {
return (
<ChatCanvas.Artifact
data={data}
getTitle={artifact => (artifact as ImageArtifact).data.caption}
iconMap={{ image: Image }}
/>
)
}
`
const initialMessages: Message[] = [
{
id: '1',
role: 'user',
content: 'Generate an image of a cat',
parts: [{ type: 'text', text: 'Generate an image of a cat' }],
},
{
id: '2',
role: 'assistant',
content:
'Here is a cat image named Millie.' +
`\n\`\`\`annotation\n${JSON.stringify({
type: 'artifact',
parts: [
{
type: 'text',
text: 'Here is a cat image named Millie.',
},
{
type: 'data-artifact',
data: {
type: 'image',
data: {
@@ -150,21 +139,24 @@ const initialMessages: Message[] = [
},
created_at: 1745480281756,
},
})}
\n\`\`\`\n`,
},
],
},
{
id: '3',
role: 'user',
content: 'Please generate a black cat image',
parts: [{ type: 'text', text: 'Please generate a black cat image' }],
},
{
id: '4',
role: 'assistant',
content:
'Here is a black cat image named Poppy.' +
`\n\`\`\`annotation\n${JSON.stringify({
type: 'artifact',
parts: [
{
type: 'text',
text: 'Here is a black cat image named Poppy.',
},
{
type: 'data-artifact',
data: {
type: 'image',
data: {
@@ -173,8 +165,8 @@ const initialMessages: Message[] = [
},
created_at: 1745480281999,
},
})}
\n\`\`\`\n`,
},
],
},
]
@@ -184,7 +176,7 @@ export default function Page(): JSX.Element {
function CustomChat() {
const { copyToClipboard, isCopied } = useCopyToClipboard({ timeout: 2000 })
const handler = useChat({ initialMessages })
const handler = useChat({ messages: initialMessages })
return (
<ChatSection
@@ -279,11 +271,8 @@ function CustomChatMessages() {
>
<ChatMessage.Avatar />
<ChatMessage.Content>
<ChatMessage.Content.Markdown
annotationRenderers={{
artifact: CustomArtifactCard,
}}
/>
<ChatMessage.Part.Markdown />
<ChatMessage.Part.Artifact />
</ChatMessage.Content>
<ChatMessage.Actions />
</ChatMessage>
@@ -292,14 +281,3 @@ function CustomChatMessages() {
</ChatMessages>
)
}
// custom artifact card for image artifacts
function CustomArtifactCard({ data }: { data: Artifact }) {
return (
<ChatCanvas.Artifact
data={data}
getTitle={artifact => (artifact as ImageArtifact).data.caption}
iconMap={{ image: Image }}
/>
)
}
File diff suppressed because one or more lines are too long
+45 -41
View File
@@ -9,7 +9,7 @@ import {
useChatUI,
useFile,
} from '@llamaindex/chat-ui'
import { Message, useChat } from 'ai/react'
import { UIMessage as Message, useChat } from '@ai-sdk/react'
import { motion, AnimatePresence } from 'framer-motion'
const code = `
@@ -21,15 +21,14 @@ import {
useChatUI,
useFile,
} from '@llamaindex/chat-ui'
import { useChat } from 'ai/react'
import { useChat } from '@ai-sdk/react'
import { motion, AnimatePresence } from 'framer-motion'
export function CustomChat() {
const handler = useChat()
const { imageUrl, getAnnotations, uploadFile, reset } = useFile({
const { image, uploadFile, reset, getAttachments } = useFile({
uploadAPI: '/chat/upload',
})
const annotations = getAnnotations()
const handleUpload = async (file: File) => {
try {
await uploadFile(file)
@@ -37,18 +36,22 @@ export function CustomChat() {
console.error(error)
}
}
const attachments = getAttachments()
return (
<ChatSection
handler={handler}
className="mx-auto h-screen max-w-3xl overflow-hidden"
className="h-screen overflow-hidden p-0 md:p-5"
>
<CustomChatMessages />
<ChatInput annotations={annotations} resetUploadedFiles={reset}>
<ChatInput
attachments={attachments}
resetUploadedFiles={reset}
>
<div>
{imageUrl ? (
{image ? (
<img
className="max-h-[100px] object-contain"
src={imageUrl}
src={image.url}
alt="uploaded"
/>
) : null}
@@ -56,6 +59,7 @@ export function CustomChat() {
<ChatInput.Form>
<ChatInput.Field />
<ChatInput.Upload
allowedExtensions={['jpg', 'png', 'jpeg']}
onUpload={handleUpload}
/>
<ChatInput.Submit />
@@ -66,7 +70,7 @@ export function CustomChat() {
}
function CustomChatMessages() {
const { messages, isLoading, append } = useChatUI()
const { messages } = useChatUI()
return (
<ChatMessages>
<ChatMessages.List className="px-0 md:px-16">
@@ -91,10 +95,9 @@ function CustomChatMessages() {
src="/llama.png"
/>
</ChatMessage.Avatar>
<ChatMessage.Content isLoading={isLoading} append={append}>
<ChatMessage.Content.Image />
<ChatMessage.Content.Markdown />
<ChatMessage.Content.DocumentFile />
<ChatMessage.Content>
<ChatMessage.Part.File />
<ChatMessage.Part.Markdown />
</ChatMessage.Content>
<ChatMessage.Actions />
</ChatMessage>
@@ -110,18 +113,22 @@ function CustomChatMessages() {
const initialMessages: Message[] = [
{
id: '1',
content: 'Generate a logo for LlamaIndex',
parts: [{ type: 'text', text: 'Generate a logo for LlamaIndex' }],
role: 'user',
},
{
id: '2',
role: 'assistant',
content:
'Got it! Here is the logo for LlamaIndex. The logo features a friendly llama mascot that represents our AI-powered document indexing and chat capabilities.',
annotations: [
parts: [
{
type: 'image',
type: 'text',
text: 'Got it! Here is the logo for LlamaIndex. The logo features a friendly llama mascot that represents our AI-powered document indexing and chat capabilities.',
},
{
type: 'data-file',
data: {
filename: 'llama.png',
mediaType: 'image/png',
url: '/llama.png',
},
},
@@ -130,24 +137,22 @@ const initialMessages: Message[] = [
{
id: '3',
role: 'user',
content: 'Show me a pdf file',
parts: [{ type: 'text', text: 'Show me a pdf file' }],
},
{
id: '4',
role: 'assistant',
content:
'Got it! Here is a sample PDF file that demonstrates PDF handling capabilities. This PDF contains some basic text and formatting examples that you can use to test PDF viewing functionality.',
annotations: [
parts: [
{
type: 'document_file',
type: 'text',
text: 'Got it! Here is a sample PDF file that demonstrates PDF handling capabilities. This PDF contains some basic text and formatting examples that you can use to test PDF viewing functionality.',
},
{
type: 'data-file',
data: {
files: [
{
id: '1',
name: 'sample.pdf',
url: 'https://pdfobject.com/pdf/sample.pdf',
},
],
filename: 'sample.pdf',
mediaType: 'application/pdf',
url: 'https://pdfobject.com/pdf/sample.pdf',
},
},
],
@@ -172,11 +177,10 @@ export default function Page(): JSX.Element {
}
function CustomChat() {
const handler = useChat({ initialMessages })
const { imageUrl, getAnnotations, uploadFile, reset } = useFile({
const handler = useChat({ messages: initialMessages })
const { image, uploadFile, reset, getAttachments } = useFile({
uploadAPI: '/chat/upload',
})
const annotations = getAnnotations()
const handleUpload = async (file: File) => {
try {
await uploadFile(file)
@@ -184,18 +188,19 @@ function CustomChat() {
console.error(error)
}
}
const attachments = getAttachments()
return (
<ChatSection
handler={handler}
className="h-screen overflow-hidden p-0 md:p-5"
>
<CustomChatMessages />
<ChatInput annotations={annotations} resetUploadedFiles={reset}>
<ChatInput attachments={attachments} resetUploadedFiles={reset}>
<div>
{imageUrl ? (
{image ? (
<img
className="max-h-[100px] object-contain"
src={imageUrl}
src={image.url}
alt="uploaded"
/>
) : null}
@@ -214,7 +219,7 @@ function CustomChat() {
}
function CustomChatMessages() {
const { messages, isLoading, append } = useChatUI()
const { messages } = useChatUI()
return (
<ChatMessages>
<ChatMessages.List className="px-0 md:px-16">
@@ -239,10 +244,9 @@ function CustomChatMessages() {
src="/llama.png"
/>
</ChatMessage.Avatar>
<ChatMessage.Content isLoading={isLoading} append={append}>
<ChatMessage.Content.Image />
<ChatMessage.Content.Markdown />
<ChatMessage.Content.DocumentFile />
<ChatMessage.Content>
<ChatMessage.Part.File />
<ChatMessage.Part.Markdown />
</ChatMessage.Content>
<ChatMessage.Actions />
</ChatMessage>
+63 -19
View File
@@ -1,13 +1,13 @@
'use client'
import { Message, useChat } from 'ai/react'
import { UIMessage as Message, useChat } from '@ai-sdk/react'
import { ChatSection } from '@llamaindex/chat-ui'
import { Code } from '@/components/code'
const code = `
import { ChatSection } from '@llamaindex/chat-ui'
import '@llamaindex/chat-ui/styles/markdown.css'
import { useChat } from 'ai/react'
import { useChat } from '@ai-sdk/react'
function DemoLatexChat() {
const handler = useChat()
@@ -18,65 +18,109 @@ function DemoLatexChat() {
const initialMessages: Message[] = [
{
role: 'user',
content: 'The product costs $10 and the discount is $5',
parts: [
{
type: 'text',
text: 'The product costs $10 and the discount is $5',
},
],
id: 'DQXPGjYiCEK1MlXg',
},
{
id: '0wR35AGp8GEDoHZu',
role: 'assistant',
content:
'If the product costs $10 and there is a discount of $5, you can calculate the final price by subtracting the discount from the original price:\n\nFinal Price = Original Price - Discount \nFinal Price = $10 - $5 \nFinal Price = $5\n\nSo, after applying the discount, the product will cost $5.',
parts: [
{
type: 'text',
text: 'If the product costs $10 and there is a discount of $5, you can calculate the final price by subtracting the discount from the original price:\n\nFinal Price = Original Price - Discount \nFinal Price = $10 - $5 \nFinal Price = $5\n\nSo, after applying the discount, the product will cost $5.',
},
],
},
{
role: 'user',
content:
'Write js code that accept a location and console log Hello from location',
parts: [
{
type: 'text',
text: 'Write js code that accept a location and console log Hello from location',
},
],
id: '2VH8xx07DxwibdFX',
},
{
id: 'Jb1Xs8w8p2RBTdUQ',
role: 'assistant',
content:
'You can create a simple JavaScript function that accepts a location as an argument and logs a message to the console. Here\'s an example of how you can do this:\n\n```javascript\nfunction greetFromLocation(location) {\n console.log(`Hello from ${location}`);\n}\n\n// Example usage:\ngreetFromLocation("New York");\ngreetFromLocation("Tokyo");\ngreetFromLocation("Paris");\n```\n\nIn this code:\n\n- The `greetFromLocation` function takes one parameter, `location`.\n- It uses template literals (the backticks ``) to create a string that includes the location.\n- The `console.log` function is used to print the message to the console.\n\nYou can call the function with different locations to see the output.',
parts: [
{
type: 'text',
text: 'You can create a simple JavaScript function that accepts a location as an argument and logs a message to the console. Here\'s an example of how you can do this:\n\n```javascript\nfunction greetFromLocation(location) {\n console.log(`Hello from ${location}`);\n}\n\n// Example usage:\ngreetFromLocation("New York");\ngreetFromLocation("Tokyo");\ngreetFromLocation("Paris");\n```\n\nIn this code:\n\n- The `greetFromLocation` function takes one parameter, `location`.\n- It uses template literals (the backticks ``) to create a string that includes the location.\n- The `console.log` function is used to print the message to the console.\n\nYou can call the function with different locations to see the output.',
},
],
},
{
role: 'user',
content: 'Formula to caculate triangle',
parts: [
{
type: 'text',
text: 'Formula to caculate triangle',
},
],
id: 'G7MEUgkjwqq0RDLk',
},
{
id: 'aonMZaAcoUglAjka',
role: 'assistant',
content:
"To calculate various properties of a triangle, you can use different formulas depending on what you want to find. Here are some common calculations:\n\n1. **Area of a Triangle**:\n - Using base and height: \n \\[\n \\text{Area} = \\frac{1}{2} \\times \\text{base} \\times \\text{height}\n \\]\n - Using Heron's formula (when you know all three sides \\(a\\), \\(b\\), and \\(c\\)):\n \\[\n s = \\frac{a + b + c}{2} \\quad \\text{(semi-perimeter)}\n \\]\n \\[\n \\text{Area} = \\sqrt{s(s-a)(s-b)(s-c)}\n \\]\n\n2. **Perimeter of a Triangle**:\n - If you know the lengths of all three sides \\(a\\), \\(b\\), and \\(c\\):\n \\[\n \\text{Perimeter} = a + b + c\n \\]\n\n3. **Pythagorean Theorem** (for right triangles):\n - If \\(c\\) is the length of the hypotenuse and \\(a\\) and \\(b\\) are the lengths of the other two sides:\n \\[\n c^2 = a^2 + b^2\n \\]\n\n4. **Angles**:\n - To find angles using the sides (Law of Cosines):\n \\[\n c^2 = a^2 + b^2 - 2ab \\cdot \\cos(C)\n \\]\n - Rearranging gives:\n \\[\n \\cos(C) = \\frac{a^2 + b^2 - c^2}{2ab}\n \\]\n\nThese formulas can help you calculate the area, perimeter, and angles of a triangle based on the information you have.",
parts: [
{
type: 'text',
text: "To calculate various properties of a triangle, you can use different formulas depending on what you want to find. Here are some common calculations:\n\n1. **Area of a Triangle**:\n - Using base and height: \n \\[\n \\text{Area} = \\frac{1}{2} \\times \\text{base} \\times \\text{height}\n \\]\n - Using Heron's formula (when you know all three sides \\(a\\), \\(b\\), and \\(c\\)):\n \\[\n s = \\frac{a + b + c}{2} \\quad \\text{(semi-perimeter)}\n \\]\n \\[\n \\text{Area} = \\sqrt{s(s-a)(s-b)(s-c)}\n \\]\n\n2. **Perimeter of a Triangle**:\n - If you know the lengths of all three sides \\(a\\), \\(b\\), and \\(c\\):\n \\[\n \\text{Perimeter} = a + b + c\n \\]\n\n3. **Pythagorean Theorem** (for right triangles):\n - If \\(c\\) is the length of the hypotenuse and \\(a\\) and \\(b\\) are the lengths of the other two sides:\n \\[\n c^2 = a^2 + b^2\n \\]\n\n4. **Angles**:\n - To find angles using the sides (Law of Cosines):\n \\[\n c^2 = a^2 + b^2 - 2ab \\cdot \\cos(C)\n \\]\n - Rearranging gives:\n \\[\n \\cos(C) = \\frac{a^2 + b^2 - c^2}{2ab}\n \\]\n\nThese formulas can help you calculate the area, perimeter, and angles of a triangle based on the information you have.",
},
],
},
{
id: 'aonMZaA22oU7lAj222',
role: 'user',
content: 'Implement calculate triangle area in js',
parts: [
{
type: 'text',
text: 'Implement calculate triangle area in js',
},
],
},
{
id: 'a2nMZaA22oU7lAj222',
role: 'assistant',
content:
'To calculate the area of a triangle in JavaScript, you can use the formula:\n\n\\[\n\\text{Area} = \\frac{1}{2} \\times \\text{base} \\times \\text{height}\n\\]\n\nHere\'s a simple implementation in JavaScript:\n\n```javascript\nfunction calculateTriangleArea(base, height) {\n if (base <= 0 || height <= 0) {\n throw new Error("Base and height must be positive numbers.");\n }\n return 0.5 * base * height;\n}\n\n// Example usage:\nconst base = 5; // Example base length\nconst height = 10; // Example height length\n\ntry {\n const area = calculateTriangleArea(base, height);\n console.log(`The area of the triangle is: ${area}`);\n} catch (error) {\n console.error(error.message);\n}\n```\n\n### Explanation:\n1. **Function Definition**: The function `calculateTriangleArea` takes two parameters: `base` and `height`.\n2. **Input Validation**: It checks if the base and height are positive numbers. If not, it throws an error.\n3. **Area Calculation**: It calculates the area using the formula and returns the result.\n4. **Example Usage**: The example shows how to call the function and log the result to the console.\n\nYou can modify the `base` and `height` variables to test with different values.',
parts: [
{
type: 'text',
text: 'To calculate the area of a triangle in JavaScript, you can use the formula:\n\n\\[\n\\text{Area} = \\frac{1}{2} \\times \\text{base} \\times \\text{height}\n\\]\n\nHere\'s a simple implementation in JavaScript:\n\n```javascript\nfunction calculateTriangleArea(base, height) {\n if (base <= 0 || height <= 0) {\n throw new Error("Base and height must be positive numbers.");\n }\n return 0.5 * base * height;\n}\n\n// Example usage:\nconst base = 5; // Example base length\nconst height = 10; // Example height length\n\ntry {\n const area = calculateTriangleArea(base, height);\n console.log(`The area of the triangle is: ${area}`);\n} catch (error) {\n console.error(error.message);\n}\n```\n\n### Explanation:\n1. **Function Definition**: The function `calculateTriangleArea` takes two parameters: `base` and `height`.\n2. **Input Validation**: It checks if the base and height are positive numbers. If not, it throws an error.\n3. **Area Calculation**: It calculates the area using the formula and returns the result.\n4. **Example Usage**: The example shows how to call the function and log the result to the console.\n\nYou can modify the `base` and `height` variables to test with different values.',
},
],
},
{
id: 'aonMZaAcoU7lAjka',
role: 'user',
content: 'Popupar formulas in Math',
parts: [
{
type: 'text',
text: 'Popupar formulas in Math',
},
],
},
{
id: 'aonMZaA22oU7lAjka',
role: 'assistant',
content:
"Here are some popular mathematical formulas across various branches of mathematics:\n\n### Algebra\n1. **Quadratic Formula**: \n \\[\n x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}\n \\]\n (Used to find the roots of a quadratic equation \\( ax^2 + bx + c = 0 \\))\n\n2. **Difference of Squares**: \n \\[\n a^2 - b^2 = (a - b)(a + b)\n \\]\n\n3. **Factoring a Perfect Square**: \n \\[\n a^2 + 2ab + b^2 = (a + b)^2\n \\]\n \\[\n a^2 - 2ab + b^2 = (a - b)^2\n \\]\n\n### Geometry\n1. **Area of a Circle**: \n \\[\n A = \\pi r^2\n \\]\n\n2. **Circumference of a Circle**: \n \\[\n C = 2\\pi r\n \\]\n\n3. **Pythagorean Theorem**: \n \\[\n a^2 + b^2 = c^2\n \\]\n (In a right triangle, where \\( c \\) is the hypotenuse)\n\n4. **Area of a Triangle**: \n \\[\n A = \\frac{1}{2} \\times \\text{base} \\times \\text{height}\n \\]\n\n### Trigonometry\n1. **Sine, Cosine, and Tangent**: \n \\[\n \\sin(\\theta) = \\frac{\\text{opposite}}{\\text{hypotenuse}}, \\quad \\cos(\\theta) = \\frac{\\text{adjacent}}{\\text{hypotenuse}}, \\quad \\tan(\\theta) = \\frac{\\text{opposite}}{\\text{adjacent}}\n \\]\n\n2. **Pythagorean Identity**: \n \\[\n \\sin^2(\\theta) + \\cos^2(\\theta) = 1\n \\]\n\n### Calculus\n1. **Derivative of a Function**: \n \\[\n \\frac{d}{dx}(x^n) = nx^{n-1}\n \\]\n\n2. **Integral of a Function**: \n \\[\n \\int x^n \\, dx = \\frac{x^{n+1}}{n+1} + C \\quad (n \\neq -1)\n \\]\n\n3. **Fundamental Theorem of Calculus**: \n \\[\n \\int_a^b f(x) \\, dx = F(b) - F(a)\n \\]\n (Where \\( F \\) is an antiderivative of \\( f \\))\n\n### Statistics\n1. **Mean**: \n \\[\n \\text{Mean} = \\frac{\\sum_{i=1}^{n} x_i}{n}\n \\]\n\n2. **Variance**: \n \\[\n \\sigma^2 = \\frac{\\sum_{i=1}^{n} (x_i - \\mu)^2}{n}\n \\]\n (Where \\( \\mu \\) is the mean)\n\n3. **Standard Deviation**: \n \\[\n \\sigma = \\sqrt{\\sigma^2}\n \\]\n\n### Probability\n1. **Probability of an Event**: \n \\[\n P(A) = \\frac{\\text{Number of favorable outcomes}}{\\text{Total number of outcomes}}\n \\]\n\n2. **Bayes' Theorem**: \n \\[\n P(A|B) = \\frac{P(B|A)P(A)}{P(B)}\n \\]\n\nThese formulas are foundational in their respective areas and are widely used in various applications of mathematics.",
parts: [
{
type: 'text',
text: "Here are some popular mathematical formulas across various branches of mathematics:\n\n### Algebra\n1. **Quadratic Formula**: \n \\[\n x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}\n \\]\n (Used to find the roots of a quadratic equation \\( ax^2 + bx + c = 0 \\))\n\n2. **Difference of Squares**: \n \\[\n a^2 - b^2 = (a - b)(a + b)\n \\]\n\n3. **Factoring a Perfect Square**: \n \\[\n a^2 + 2ab + b^2 = (a + b)^2\n \\]\n \\[\n a^2 - 2ab + b^2 = (a - b)^2\n \\]\n\n### Geometry\n1. **Area of a Circle**: \n \\[\n A = \\pi r^2\n \\]\n\n2. **Circumference of a Circle**: \n \\[\n C = 2\\pi r\n \\]\n\n3. **Pythagorean Theorem**: \n \\[\n a^2 + b^2 = c^2\n \\]\n (In a right triangle, where \\( c \\) is the hypotenuse)\n\n4. **Area of a Triangle**: \n \\[\n A = \\frac{1}{2} \\times \\text{base} \\times \\text{height}\n \\]\n\n### Trigonometry\n1. **Sine, Cosine, and Tangent**: \n \\[\n \\sin(\\theta) = \\frac{\\text{opposite}}{\\text{hypotenuse}}, \\quad \\cos(\\theta) = \\frac{\\text{adjacent}}{\\text{hypotenuse}}, \\quad \\tan(\\theta) = \\frac{\\text{opposite}}{\\text{adjacent}}\n \\]\n\n2. **Pythagorean Identity**: \n \\[\n \\sin^2(\\theta) + \\cos^2(\\theta) = 1\n \\]\n\n### Calculus\n1. **Derivative of a Function**: \n \\[\n \\frac{d}{dx}(x^n) = nx^{n-1}\n \\]\n\n2. **Integral of a Function**: \n \\[\n \\int x^n \\, dx = \\frac{x^{n+1}}{n+1} + C \\quad (n \\neq -1)\n \\]\n\n3. **Fundamental Theorem of Calculus**: \n \\[\n \\int_a^b f(x) \\, dx = F(b) - F(a)\n \\]\n (Where \\( F \\) is an antiderivative of \\( f \\))\n\n### Statistics\n1. **Mean**: \n \\[\n \\text{Mean} = \\frac{\\sum_{i=1}^{n} x_i}{n}\n \\]\n\n2. **Variance**: \n \\[\n \\sigma^2 = \\frac{\\sum_{i=1}^{n} (x_i - \\mu)^2}{n}\n \\]\n (Where \\( \\mu \\) is the mean)\n\n3. **Standard Deviation**: \n \\[\n \\sigma = \\sqrt{\\sigma^2}\n \\]\n\n### Probability\n1. **Probability of an Event**: \n \\[\n P(A) = \\frac{\\text{Number of favorable outcomes}}{\\text{Total number of outcomes}}\n \\]\n\n2. **Bayes' Theorem**: \n \\[\n P(A|B) = \\frac{P(B|A)P(A)}{P(B)}\n \\]\n\nThese formulas are foundational in their respective areas and are widely used in various applications of mathematics.",
},
],
},
]
export default function Page(): JSX.Element {
const handler = useChat({ initialMessages })
const handler = useChat({ messages: initialMessages })
return (
<div className="flex gap-10">
<div className="hidden w-1/3 justify-center space-y-10 self-center p-10 md:block">
+37 -27
View File
@@ -7,13 +7,13 @@ import {
ChatSection,
useChatUI,
} from '@llamaindex/chat-ui'
import { Message, useChat } from 'ai/react'
import { UIMessage as Message, useChat } from '@ai-sdk/react'
import { Code } from '@/components/code'
import MermaidDiagram from './mermaid-diagram'
const code = `
import { ChatSection, ChatInput, ChatMessage, ChatMessages, useChatUI } from '@llamaindex/chat-ui'
import { useChat } from 'ai/react'
import { useChat } from '@ai-sdk/react'
// This demo requires mermaid to be installed in your project:
// pnpm add mermaid
@@ -53,31 +53,41 @@ const initialMessages: Message[] = [
{
id: '1',
role: 'user',
content: 'Show me a system architecture diagram',
parts: [
{
type: 'text',
text: 'Show me a system architecture diagram',
},
],
},
{
id: '2',
role: 'assistant',
content: [
'Here is a system architecture diagram showing how LlamaIndex ChatUI components interact:',
'',
'```mermaid',
'graph TD',
' A[User] -->|Input| B[ChatInput]',
' B -->|Process| C[ChatSection]',
' C -->|Render| D[ChatMessages]',
' D -->|Display| E[ChatMessage]',
' E -->|Show| F[Content]',
' F -->|Render| G[Markdown]',
' F -->|Render| H[Images]',
' F -->|Render| I[Documents]',
' F -->|Render| J[Mermaid]',
' style A fill:#f9f,stroke:#333,stroke-width:2px',
' style B fill:#bbf,stroke:#333,stroke-width:2px',
' style C fill:#dfd,stroke:#333,stroke-width:2px',
'```',
'',
].join('\n'),
parts: [
{
type: 'text',
text: [
'Here is a system architecture diagram showing how LlamaIndex ChatUI components interact:',
'',
'```mermaid',
'graph TD',
' A[User] -->|Input| B[ChatInput]',
' B -->|Process| C[ChatSection]',
' C -->|Render| D[ChatMessages]',
' D -->|Display| E[ChatMessage]',
' E -->|Show| F[Content]',
' F -->|Render| G[Markdown]',
' F -->|Render| H[Images]',
' F -->|Render| I[Documents]',
' F -->|Render| J[Mermaid]',
' style A fill:#f9f,stroke:#333,stroke-width:2px',
' style B fill:#bbf,stroke:#333,stroke-width:2px',
' style C fill:#dfd,stroke:#333,stroke-width:2px',
'```',
'',
].join('\n'),
},
],
},
]
@@ -96,7 +106,7 @@ export default function MermaidDemoPage(): JSX.Element {
}
function MermaidChat() {
const handler = useChat({ initialMessages })
const handler = useChat({ messages: initialMessages })
return (
<ChatSection handler={handler} className="h-full">
<MermaidChatMessages />
@@ -106,7 +116,7 @@ function MermaidChat() {
}
function MermaidChatMessages() {
const { messages, isLoading, append } = useChatUI()
const { messages } = useChatUI()
return (
<ChatMessages>
<ChatMessages.List>
@@ -118,8 +128,8 @@ function MermaidChatMessages() {
className="items-start"
>
<ChatMessage.Avatar />
<ChatMessage.Content isLoading={isLoading} append={append}>
<ChatMessage.Content.Markdown
<ChatMessage.Content>
<ChatMessage.Part.Markdown
languageRenderers={{ mermaid: MermaidDiagram }}
/>
</ChatMessage.Content>
+2 -2
View File
@@ -1,12 +1,12 @@
'use client'
import { useChat } from 'ai/react'
import { useChat } from '@ai-sdk/react'
import { ChatSection } from '@llamaindex/chat-ui'
import { Code } from '@/components/code'
const code = `
import { ChatSection } from '@llamaindex/chat-ui'
import { useChat } from 'ai/react'
import { useChat } from "@ai-sdk/react";
function SimpleChat() {
const handler = useChat()
@@ -0,0 +1,203 @@
'use client'
import { Markdown } from '@llamaindex/chat-ui/widgets'
const sampleMarkdown = `# Custom Markdown Components Example
This example demonstrates how to customize the **Markdown** component using the new \`components\` prop.
## Custom Headings
### This is a custom H3 heading
#### And this is a custom H4 heading
## Custom Paragraphs
This paragraph will be rendered with custom styling. The new components prop allows you to override any markdown element with your own React components.
You can customize:
- **Headings** (h1, h2, h3, h4, h5, h6)
- **Paragraphs** and text formatting
- **Links** and navigation
- **Lists** (both ordered and unordered)
- **Code blocks** and inline code
- **Images** and media
- **Tables** and data display
- **Blockquotes** and emphasis
## Custom Links
Check out this [custom styled link](https://llamaindex.ai) that opens with special behavior.
## Custom Code Examples
Here's some inline \`custom code\` and a code block:
\`\`\`javascript
// This code block uses the default renderer
function defaultRenderer() {
return "Default code rendering";
}
\`\`\`
\`\`\`mermaid
graph TD
A[Custom Components] --> B[Better UX]
A --> C[Consistent Design]
B --> D[Happy Users]
C --> D
\`\`\`
## Custom Lists
- First custom list item
- Second custom list item with **bold text**
- Third item with [a link](https://example.com)
1. Numbered custom item
2. Another numbered item
3. Final numbered item
## Custom Blockquotes
> This is a custom blockquote that can have special styling and behavior.
>
> It can span multiple lines and include **formatting**.
---
*This entire markdown content is rendered with custom components!*
## Custom Language Renderer
This example also shows custom language renderers - notice how the mermaid diagram above gets special treatment!
`
// Custom component implementations
const customComponents = {
// Custom heading components with special styling
h1: ({ children }: { children: React.ReactNode }) => (
<h1 className="mb-4 border-b-2 border-blue-200 bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text pb-2 text-4xl font-bold text-transparent">
🚀 {children}
</h1>
),
h2: ({ children }: { children: React.ReactNode }) => (
<h2 className="mb-3 mt-6 flex items-center gap-2 text-3xl font-semibold text-blue-700">
<span className="h-6 w-2 rounded bg-blue-500" />
{children}
</h2>
),
h3: ({ children }: { children: React.ReactNode }) => (
<h3 className="mb-3 mt-5 flex items-center gap-2 text-2xl font-medium text-purple-600">
<span className="h-5 w-1.5 rounded bg-purple-400" />
{children}
</h3>
),
h4: ({ children }: { children: React.ReactNode }) => (
<h4 className="mb-2 mt-4 flex items-center gap-2 text-xl font-medium text-green-600">
<span className="h-4 w-1 rounded bg-green-400" />
{children}
</h4>
),
// Custom paragraph with special styling
p: ({ children }: { children: React.ReactNode }) => (
<div className="mb-4 rounded-lg border-l-4 border-blue-200 bg-gray-50 p-4 leading-relaxed text-gray-700 dark:border-blue-700 dark:bg-gray-800/50 dark:text-gray-300">
{children}
</div>
),
// Custom link component with hover effects
a: ({ href, children }: { href?: string; children: React.ReactNode }) => (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 rounded px-1 py-0.5 font-medium text-blue-600 underline decoration-blue-300 decoration-2 transition-all duration-200 hover:bg-blue-50 hover:text-blue-800 hover:decoration-blue-500 dark:text-blue-400 dark:hover:bg-blue-900/20 dark:hover:text-blue-300"
onClick={() => {
console.log('Custom link clicked:', href)
}}
>
{children}
<span className="text-xs">🔗</span>
</a>
),
// Custom list components
ul: ({ children }: { children: React.ReactNode }) => (
<ul className="mb-4 space-y-2 rounded-lg border border-green-200 bg-gradient-to-r from-green-50 to-blue-50 p-4 dark:border-green-700 dark:from-green-900/10 dark:to-blue-900/10">
{children}
</ul>
),
ol: ({ children }: { children: React.ReactNode }) => (
<ol className="mb-4 space-y-2 rounded-lg border border-purple-200 bg-gradient-to-r from-purple-50 to-pink-50 p-4 dark:border-purple-700 dark:from-purple-900/10 dark:to-pink-900/10">
{children}
</ol>
),
li: ({ children }: { children: React.ReactNode }) => (
<li className="flex items-start gap-2">
<span className="mt-1 font-bold text-blue-500"></span>
<span className="text-gray-700 dark:text-gray-300">{children}</span>
</li>
),
// Custom blockquote
blockquote: ({ children }: { children: React.ReactNode }) => (
<blockquote className="mb-4 rounded-r-lg border-l-4 border-yellow-400 bg-gradient-to-r from-yellow-50 to-orange-50 p-4 italic dark:from-yellow-900/10 dark:to-orange-900/10">
<div className="flex items-start gap-3">
<span className="text-2xl text-yellow-500">💡</span>
<div className="text-gray-700 dark:text-gray-300">{children}</div>
</div>
</blockquote>
),
// Custom code block (inline)
code: ({
inline,
children,
}: {
inline?: boolean
children: React.ReactNode
}) => {
if (inline) {
return (
<code className="rounded border border-purple-200 bg-purple-100 px-2 py-1 font-mono text-sm text-purple-800 dark:border-purple-700 dark:bg-purple-900/30 dark:text-purple-300">
{children}
</code>
)
}
// For block code, let the default handler take over
return <code>{children}</code>
},
// Custom horizontal rule
hr: () => (
<div className="my-8 flex items-center gap-4">
<div className="h-0.5 flex-1 bg-gradient-to-r from-transparent via-blue-300 to-transparent" />
<span className="text-xl text-blue-500"></span>
<div className="h-0.5 flex-1 bg-gradient-to-r from-transparent via-blue-300 to-transparent" />
</div>
),
// Custom strong/bold text
strong: ({ children }: { children: React.ReactNode }) => (
<strong className="rounded bg-blue-50 px-1 py-0.5 font-bold text-blue-700 dark:bg-blue-900/20 dark:text-blue-300">
{children}
</strong>
),
// Custom emphasis/italic text
em: ({ children }: { children: React.ReactNode }) => (
<em className="rounded bg-purple-50 px-1 py-0.5 italic text-purple-600 dark:bg-purple-900/20 dark:text-purple-400">
{children}
</em>
),
}
export default function CustomMarkdownPage() {
return (
<div className="container mx-auto max-w-4xl p-6">
<div className="rounded-lg border border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-900">
<Markdown content={sampleMarkdown} components={customComponents} />
</div>
</div>
)
}
+58 -36
View File
@@ -1,12 +1,36 @@
import { faker } from '@faker-js/faker'
const DATA_PREFIX = 'data: ' // SSE format prefix
const TOKEN_DELAY = 30 // 30ms delay between tokens
export type TextChunk = {
type: 'text-delta' | 'text-start' | 'text-end'
id: string
delta?: string
}
export type DataChunk = {
type: `data-${string}` // requires `data-` prefix when sending data parts
data: Record<string, any>
}
const encoder = new TextEncoder()
export const writeStream = (
controller: ReadableStreamDefaultController,
chunk: TextChunk | DataChunk
) => {
controller.enqueue(
encoder.encode(`${DATA_PREFIX}${JSON.stringify(chunk)}\n\n`)
)
}
export const fakeStreamText = ({
chunkCount = 10,
streamProtocol = 'data',
}: {
chunkCount?: number
streamProtocol?: 'data' | 'text'
} = {}) => {
// Generate sample text blocks
const blocks = [
Array.from({ length: chunkCount }, () => ({
delay: faker.number.int({ max: 100, min: 30 }),
@@ -18,51 +42,49 @@ export const fakeStreamText = ({
})),
]
const encoder = new TextEncoder()
return new ReadableStream({
async start(controller) {
async function writeTextMessage(content: string) {
// Generate a unique message id
const messageId = crypto.randomUUID()
// Start the text chunk
const startChunk: TextChunk = { id: messageId, type: 'text-start' }
writeStream(controller, startChunk)
// Stream tokens one by one
for (const token of content.split(' ')) {
if (token.trim()) {
const deltaChunk: TextChunk = {
id: messageId,
type: 'text-delta',
delta: `${token} `,
}
writeStream(controller, deltaChunk)
await new Promise(resolve => setTimeout(resolve, TOKEN_DELAY))
}
}
// End the text chunk
const endChunk: TextChunk = { id: messageId, type: 'text-end' }
writeStream(controller, endChunk)
}
// Stream each block as a separate message
for (let i = 0; i < blocks.length; i++) {
const block = blocks[i]
for (const chunk of block) {
await new Promise(resolve => setTimeout(resolve, chunk.delay))
// Combine all texts in the block into one message
const blockText = block.map(chunk => chunk.texts).join('')
if (streamProtocol === 'text') {
controller.enqueue(encoder.encode(chunk.texts))
} else {
controller.enqueue(
encoder.encode(`0:${JSON.stringify(chunk.texts)}\n`)
)
}
}
await writeTextMessage(blockText)
// Add paragraph break between blocks
if (i < blocks.length - 1) {
if (streamProtocol === 'text') {
controller.enqueue(encoder.encode('\n\n'))
} else {
controller.enqueue(encoder.encode(`0:${JSON.stringify('\n\n')}\n`))
}
await writeTextMessage('\n\n')
}
}
if (streamProtocol === 'data') {
controller.enqueue(
encoder.encode(
`d:${JSON.stringify({
finishReason: 'stop',
usage: {
promptTokens: 0,
completionTokens: blocks.reduce(
(sum, block) => sum + block.length,
0
),
},
})}\n`
)
)
}
controller.close()
},
})
+4 -3
View File
@@ -1,6 +1,6 @@
{
"name": "web",
"version": "1.0.49",
"version": "1.1.1",
"private": true,
"scripts": {
"dev": "next dev",
@@ -18,14 +18,15 @@
"@llamaindex/dynamic-ui": "workspace:*",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tabs": "^1.1.1",
"ai": "4.0.0",
"@ai-sdk/react": "^2.0.4",
"ai": "^5.0.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"highlight.js": "^11.10.0",
"llamaindex": "0.9.3",
"lucide-react": "^0.453.0",
"mermaid": "^11.6.0",
"next": "15.1.7",
"next": "15.1.11",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"tailwind-merge": "^2.5.4",
+1 -1
View File
@@ -11,7 +11,7 @@
"files": [
{
"path": "registry/chat/chat.tsx",
"content": "'use client'\r\n\r\nimport {\r\n ChatHandler,\r\n ChatSection as ChatSectionUI,\r\n Message,\r\n} from '@llamaindex/chat-ui'\r\n\r\nimport '@llamaindex/chat-ui/styles/markdown.css'\r\nimport '@llamaindex/chat-ui/styles/pdf.css'\r\nimport '@llamaindex/chat-ui/styles/editor.css'\r\nimport { useState } from 'react'\r\n\r\nconst initialMessages: Message[] = [\r\n {\r\n content: 'Write simple Javascript hello world code',\r\n role: 'user',\r\n },\r\n {\r\n role: 'assistant',\r\n content:\r\n 'Got it! Here\\'s the simplest JavaScript code to print \"Hello, World!\" to the console:\\n\\n```javascript\\nconsole.log(\"Hello, World!\");\\n```\\n\\nYou can run this code in any JavaScript environment, such as a web browser\\'s console or a Node.js environment. Just paste the code and execute it to see the output.',\r\n },\r\n {\r\n content: 'Write a simple math equation',\r\n role: 'user',\r\n },\r\n {\r\n role: 'assistant',\r\n content:\r\n \"Let's explore a simple mathematical equation using LaTeX:\\n\\n The quadratic formula is: $$x = \\\\frac{-b \\\\pm \\\\sqrt{b^2 - 4ac}}{2a}$$\\n\\nThis formula helps us solve quadratic equations in the form $ax^2 + bx + c = 0$. The solution gives us the x-values where the parabola intersects the x-axis.\",\r\n },\r\n]\r\n\r\nexport function ChatSection() {\r\n // You can replace the handler with a useChat hook from Vercel AI SDK\r\n const handler = useMockChat(initialMessages)\r\n return (\r\n <div className=\"flex max-h-[80vh] flex-col gap-6 overflow-y-auto\">\r\n <ChatSectionUI handler={handler} />\r\n </div>\r\n )\r\n}\r\n\r\nfunction useMockChat(initMessages: Message[]): ChatHandler {\r\n const [messages, setMessages] = useState<Message[]>(initMessages)\r\n const [input, setInput] = useState('')\r\n const [isLoading, setIsLoading] = useState(false)\r\n\r\n const append = async (message: Message) => {\r\n setIsLoading(true)\r\n\r\n const mockResponse: Message = {\r\n role: 'assistant',\r\n content: '',\r\n }\r\n setMessages(prev => [...prev, message, mockResponse])\r\n\r\n const mockContent =\r\n 'This is a mock response. In a real implementation, this would be replaced with an actual AI response.'\r\n\r\n let streamedContent = ''\r\n const words = mockContent.split(' ')\r\n\r\n for (const word of words) {\r\n await new Promise(resolve => setTimeout(resolve, 100))\r\n streamedContent += (streamedContent ? ' ' : '') + word\r\n setMessages(prev => {\r\n return [\r\n ...prev.slice(0, -1),\r\n {\r\n role: 'assistant',\r\n content: streamedContent,\r\n },\r\n ]\r\n })\r\n }\r\n\r\n setIsLoading(false)\r\n return mockContent\r\n }\r\n\r\n return {\r\n messages,\r\n input,\r\n setInput,\r\n isLoading,\r\n append,\r\n }\r\n}\r\n",
"content": "'use client'\n\nimport {\n ChatHandler,\n ChatSection as ChatSectionUI,\n Message,\n} from '@llamaindex/chat-ui'\n\nimport '@llamaindex/chat-ui/styles/markdown.css'\nimport '@llamaindex/chat-ui/styles/pdf.css'\nimport '@llamaindex/chat-ui/styles/editor.css'\nimport { useState } from 'react'\n\nconst initialMessages: Message[] = [\n {\n id: '1',\n parts: [{ type: 'text', text: 'Write simple Javascript hello world code' }],\n role: 'user',\n },\n {\n id: '2',\n role: 'assistant',\n parts: [\n {\n type: 'text',\n text: 'Got it! Here\\'s the simplest JavaScript code to print \"Hello, World!\" to the console:\\n\\n```javascript\\nconsole.log(\"Hello, World!\");\\n```\\n\\nYou can run this code in any JavaScript environment, such as a web browser\\'s console or a Node.js environment. Just paste the code and execute it to see the output.',\n },\n ],\n },\n {\n id: '3',\n parts: [{ type: 'text', text: 'Write a simple math equation' }],\n role: 'user',\n },\n {\n id: '4',\n role: 'assistant',\n parts: [\n {\n type: 'text',\n text: \"Let's explore a simple mathematical equation using LaTeX:\\n\\n The quadratic formula is: $$x = \\\\frac{-b \\\\pm \\\\sqrt{b^2 - 4ac}}{2a}$$\\n\\nThis formula helps us solve quadratic equations in the form $ax^2 + bx + c = 0$. The solution gives us the x-values where the parabola intersects the x-axis.\",\n },\n ],\n },\n]\n\nexport function ChatSection() {\n // You can replace the handler with a useChat hook from Vercel AI SDK\n const handler = useMockChat(initialMessages)\n return (\n <div className=\"flex max-h-[80vh] flex-col gap-6 overflow-y-auto\">\n <ChatSectionUI handler={handler} />\n </div>\n )\n}\n\nfunction useMockChat(initMessages: Message[]): ChatHandler {\n const [messages, setMessages] = useState<Message[]>(initMessages)\n const [status, setStatus] = useState<\n 'streaming' | 'ready' | 'error' | 'submitted'\n >('ready')\n\n const append = async (message: Message) => {\n const mockResponse: Message = {\n id: '5',\n role: 'assistant',\n parts: [{ type: 'text', text: '' }],\n }\n setMessages(prev => [...prev, message, mockResponse])\n\n const mockContent =\n 'This is a mock response. In a real implementation, this would be replaced with an actual AI response.'\n\n let streamedContent = ''\n const words = mockContent.split(' ')\n\n for (const word of words) {\n await new Promise(resolve => setTimeout(resolve, 100))\n streamedContent += (streamedContent ? ' ' : '') + word\n setMessages(prev => {\n return [\n ...prev.slice(0, -1),\n {\n id: '6',\n role: 'assistant',\n parts: [{ type: 'text', text: streamedContent }],\n },\n ]\n })\n }\n\n return mockContent\n }\n\n return {\n messages,\n status,\n sendMessage: async (message: Message) => {\n setStatus('submitted')\n await append(message)\n setStatus('ready')\n },\n }\n}\n",
"type": "registry:block"
}
],
+31 -17
View File
@@ -13,22 +13,34 @@ import { useState } from 'react'
const initialMessages: Message[] = [
{
content: 'Write simple Javascript hello world code',
id: '1',
parts: [{ type: 'text', text: 'Write simple Javascript hello world code' }],
role: 'user',
},
{
id: '2',
role: 'assistant',
content:
'Got it! Here\'s the simplest JavaScript code to print "Hello, World!" to the console:\n\n```javascript\nconsole.log("Hello, World!");\n```\n\nYou can run this code in any JavaScript environment, such as a web browser\'s console or a Node.js environment. Just paste the code and execute it to see the output.',
parts: [
{
type: 'text',
text: 'Got it! Here\'s the simplest JavaScript code to print "Hello, World!" to the console:\n\n```javascript\nconsole.log("Hello, World!");\n```\n\nYou can run this code in any JavaScript environment, such as a web browser\'s console or a Node.js environment. Just paste the code and execute it to see the output.',
},
],
},
{
content: 'Write a simple math equation',
id: '3',
parts: [{ type: 'text', text: 'Write a simple math equation' }],
role: 'user',
},
{
id: '4',
role: 'assistant',
content:
"Let's explore a simple mathematical equation using LaTeX:\n\n The quadratic formula is: $$x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}$$\n\nThis formula helps us solve quadratic equations in the form $ax^2 + bx + c = 0$. The solution gives us the x-values where the parabola intersects the x-axis.",
parts: [
{
type: 'text',
text: "Let's explore a simple mathematical equation using LaTeX:\n\n The quadratic formula is: $$x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}$$\n\nThis formula helps us solve quadratic equations in the form $ax^2 + bx + c = 0$. The solution gives us the x-values where the parabola intersects the x-axis.",
},
],
},
]
@@ -44,15 +56,15 @@ export function ChatSection() {
function useMockChat(initMessages: Message[]): ChatHandler {
const [messages, setMessages] = useState<Message[]>(initMessages)
const [input, setInput] = useState('')
const [isLoading, setIsLoading] = useState(false)
const [status, setStatus] = useState<
'streaming' | 'ready' | 'error' | 'submitted'
>('ready')
const append = async (message: Message) => {
setIsLoading(true)
const mockResponse: Message = {
id: '5',
role: 'assistant',
content: '',
parts: [{ type: 'text', text: '' }],
}
setMessages(prev => [...prev, message, mockResponse])
@@ -69,22 +81,24 @@ function useMockChat(initMessages: Message[]): ChatHandler {
return [
...prev.slice(0, -1),
{
id: '6',
role: 'assistant',
content: streamedContent,
parts: [{ type: 'text', text: streamedContent }],
},
]
})
}
setIsLoading(false)
return mockContent
}
return {
messages,
input,
setInput,
isLoading,
append,
status,
sendMessage: async (message: Message) => {
setStatus('submitted')
await append(message)
setStatus('ready')
},
}
}
+12
View File
@@ -1,5 +1,17 @@
# @llamaindex/chat-ui-docs
## 0.1.0
### Minor Changes
- 1ceb4ba: support vercel ai sdk ver 5
## 0.0.9
### Patch Changes
- f726f19: fix: missing document for ChatSection
## 0.0.8
### Patch Changes
-435
View File
@@ -1,435 +0,0 @@
---
title: Annotations
description: Working with rich content annotations for multimedia and interactive chat experiences
---
Annotations are the key to creating rich, interactive chat experiences beyond simple text. They allow you to embed images, files, sources, events, and custom content types directly into chat messages.
## Annotation System Overview
Annotations are structured data attached to messages that widgets can render as rich content. The system supports both built-in annotation types and custom annotations for domain-specific content.
### Message Structure with Annotations
```typescript
interface Message {
id: string
role: 'user' | 'assistant' | 'system'
content: string
annotations?: JSONValue[]
}
```
### Built-in Annotation Types
The library provides several built-in annotation types:
- **IMAGE** - Image data with URLs
- **DOCUMENT_FILE** - File attachments and metadata
- **SOURCES** - Citation and source references
- **EVENTS** - Process events and function calls
- **AGENT_EVENTS** - Agent-specific events with progress
- **ARTIFACT** - Interactive code and document artifacts
- **SUGGESTED_QUESTIONS** - Follow-up question suggestions
## Using Annotations
Annotations automatically render when using the `annotations` property on a message. Here's an example of how to render an image annotation:
```tsx
const handler = useChat({
initialMessages: [
{
role: 'assistant',
content: 'Here is an image',
annotations: [
{
type: 'image',
data: {
url: '/llama.png',
},
},
],
}
})
return (
<ChatSection
handler={handler}
className="block h-full flex-row gap-4 p-0 md:flex md:p-5"
>
<ChatMessage message={message}>
<ChatMessage.Content>
<ChatMessage.Content.Markdown />
<ChatMessage.Content.Image />{' '}
{/* Automatically renders IMAGE annotations */}
</ChatMessage.Content>
</ChatMessage>
</ChatSection>
)
```
In the example above, the `ChatMessage.Content.Image` component automatically renders the image annotation retrieved from the `annotations` property on the message which is retrieved by the `useChatMessage` hook.
The annotation is then passed to the `ChatImage` component which renders the image.
## File Annotations
Display file attachments with download links and preview capabilities.
### Document File Annotations
```typescript
const fileAnnotation = {
type: 'DOCUMENT_FILE',
data: {
files: [
{
id: 'doc1',
name: 'quarterly-report.pdf',
type: 'application/pdf',
url: '/files/quarterly-report.pdf',
size: 2048576, // 2MB in bytes
metadata: {
title: 'Q4 2024 Quarterly Report',
author: 'Finance Team',
pages: 25,
},
},
{
id: 'doc2',
name: 'data-analysis.csv',
type: 'text/csv',
url: '/files/data-analysis.csv',
size: 1024000,
metadata: {
rows: 5000,
columns: 12,
},
},
],
},
}
```
## Source Annotations
Display citations and source references with document grouping.
### Creating Source Annotations
```typescript
const sourceAnnotation = {
type: 'sources',
data: {
nodes: [
{
id: 'source1',
url: '/documents/research-paper.pdf',
metadata: {
title: 'Machine Learning in Healthcare',
author: 'Dr. Jane Smith',
page_number: 15,
section: 'Methodology',
published_date: '2024-01-15',
},
},
{
id: 'source2',
url: '/documents/clinical-study.pdf',
metadata: {
title: 'Clinical Trial Results',
author: 'Medical Research Institute',
page_number: 8,
figure: 'Figure 3.2',
},
},
],
},
}
```
### Citation in Content
Reference sources directly in your content using citation syntax:
```typescript
const content = `
Based on recent research [^1], machine learning shows promising results
in medical diagnosis. The clinical trial data [^2] supports these findings
with a 95% accuracy rate.
[^1]: Machine Learning in Healthcare, p. 15
[^2]: Clinical Trial Results, Figure 3.2
`
return {
role: 'assistant',
content,
annotations: [sourceAnnotation],
}
```
## Event Annotations
Display process events, function calls, and system activities.
### Basic Events
```typescript
const eventAnnotation = {
type: 'events',
data: [
{
type: 'function_call',
name: 'search_database',
args: {
query: 'machine learning papers',
limit: 10,
},
result: 'Found 8 relevant papers',
timestamp: '2024-01-15T10:30:00Z',
},
{
type: 'tool_use',
name: 'calculate_statistics',
args: {
dataset: 'user_engagement',
},
result: {
mean: 4.2,
median: 4.1,
std_dev: 0.8,
},
},
],
}
```
### Agent Events with Progress
```typescript
const agentEventAnnotation = {
type: 'agent_events',
data: {
agent_name: 'Research Assistant',
total_steps: 4,
current_step: 2,
progress: 50,
events: [
{
step: 1,
name: 'Search Documents',
status: 'completed',
result: 'Found 15 relevant documents',
},
{
step: 2,
name: 'Analyze Content',
status: 'in_progress',
progress: 75,
},
{
step: 3,
name: 'Generate Summary',
status: 'pending',
},
{
step: 4,
name: 'Create Recommendations',
status: 'pending',
},
],
},
}
```
## Artifact Annotations
Create interactive code and document artifacts that users can edit.
### Code Artifacts
```typescript
const codeArtifact = {
type: 'artifact',
data: {
type: 'code',
data: {
title: 'Data Analysis Script',
file_name: 'analyze_data.py',
language: 'python',
code: `
import pandas as pd
import matplotlib.pyplot as plt
def analyze_sales_data(file_path):
# Load data
df = pd.read_csv(file_path)
# Calculate monthly totals
monthly_sales = df.groupby('month')['sales'].sum()
# Create visualization
plt.figure(figsize=(10, 6))
monthly_sales.plot(kind='bar')
plt.title('Monthly Sales Analysis')
plt.ylabel('Sales ($)')
plt.show()
return monthly_sales
# Usage
sales_data = analyze_sales_data('sales.csv')
print(sales_data)
`,
},
},
}
```
### Document Artifacts
```typescript
const documentArtifact = {
type: 'artifact',
data: {
type: 'document',
data: {
title: 'Project Proposal',
content: `
# AI-Powered Analytics Platform
## Executive Summary
This proposal outlines the development of an AI-powered analytics platform
designed to help businesses make data-driven decisions.
## Key Features
- **Real-time Data Processing**: Stream analytics with sub-second latency
- **Machine Learning Models**: Automated insight generation
- **Interactive Dashboards**: Self-service analytics for business users
## Implementation Timeline
### Phase 1 (Months 1-3)
- Core platform development
- Basic ML model integration
### Phase 2 (Months 4-6)
- Advanced analytics features
- Dashboard creation tools
## Budget Estimate
Total project cost: $250,000
`,
},
},
}
```
## Suggested Questions
Provide interactive follow-up questions to guide the conversation.
```typescript
const suggestedQuestionsAnnotation = {
type: 'suggested_questions',
data: {
questions: [
'Can you explain the methodology in more detail?',
'What are the potential limitations of this approach?',
'How does this compare to traditional methods?',
'What would be the next steps for implementation?',
],
},
}
```
## Custom Annotations
Create domain-specific annotations for specialized content.
### Weather Widget Example
```typescript
// Define custom annotation type
interface WeatherAnnotation {
type: 'weather'
data: {
location: string
temperature: number
condition: string
humidity: number
windSpeed: number
forecast?: Array<{
day: string
high: number
low: number
condition: string
}>
}
}
// Create annotation
const weatherAnnotation: WeatherAnnotation = {
type: 'weather',
data: {
location: 'San Francisco, CA',
temperature: 22,
condition: 'sunny',
humidity: 65,
windSpeed: 12,
forecast: [
{ day: 'Tomorrow', high: 24, low: 18, condition: 'cloudy' },
{ day: 'Wednesday', high: 26, low: 20, condition: 'sunny' },
],
},
}
```
### Custom Widget Implementation
```tsx
import { useChatMessage, getAnnotationData } from '@llamaindex/chat-ui'
interface WeatherData {
location: string
temperature: number
condition: string
humidity: number
windSpeed: number
}
function WeatherWidget() {
const { message } = useChatMessage()
const weatherData = getAnnotationData<WeatherData>(message, 'weather')
if (!weatherData?.[0]) return null
const data = weatherData[0]
// Render weather data...
}
```
## Annotation Utilities
### getAnnotationData
Extract annotation data by type from messages:
```tsx
import { getAnnotationData } from '@llamaindex/chat-ui'
// Usage
return getAnnotationData<WeatherData>(message, 'weather')
```
## Next Steps
- [Artifacts](./artifacts.mdx) - Learn about interactive code and document artifacts
- [Widgets](./widgets.mdx) - Explore widget implementation details
- [Examples](./examples.mdx) - See complete annotation examples
- [Customization](./customization.mdx) - Style and customize annotation appearance
+35 -44
View File
@@ -28,9 +28,9 @@ Code artifacts provide interactive code editing with full syntax highlighting an
### Creating Code Artifacts
```typescript
// Server-side: Create code artifact annotation
// Server-side: Create code artifact part
const codeArtifact = {
type: 'artifact',
type: 'data-artifact',
data: {
type: 'code',
data: {
@@ -106,7 +106,7 @@ export async function POST(request: Request) {
// Send code artifact
const artifact = {
type: 'artifact',
type: 'data-artifact',
data: {
type: 'code',
data: {
@@ -118,11 +118,8 @@ export async function POST(request: Request) {
},
}
// wrap the annotation in a code block with the language key is 'annotation'
const codeBlock = `\n\`\`\`annotation\n${JSON.stringify(codeArtifact)}\n\`\`\`\n`
// send the artifact with the 0: prefix to make it inline
controller.enqueue(encoder.encode(`0:${JSON.stringify(codeBlock)}\\n`))
// send the artifact with the data: prefix for SSE format
controller.enqueue(encoder.encode(`data: ${JSON.stringify(artifact)}\\n`))
// Send follow-up text
controller.enqueue(
@@ -137,8 +134,8 @@ export async function POST(request: Request) {
return new Response(stream, {
headers: {
'Content-Type': 'text/plain; charset=utf-8',
'X-Vercel-AI-Data-Stream': 'v1',
'Content-Type': 'text/event-stream',
'Connection': 'keep-alive',
},
})
}
@@ -178,7 +175,7 @@ Document artifacts provide rich text editing with markdown support and real-time
```typescript
const documentArtifact = {
type: 'artifact',
type: 'data-artifact',
data: {
type: 'document',
data: {
@@ -372,7 +369,11 @@ Document artifacts provide:
import { ChatSection, ChatCanvas } from '@llamaindex/chat-ui'
function ChatWithCanvas() {
const handler = useChat({ api: '/api/chat' })
const handler = useChat({
transport: new DefaultChatTransport({
api: '/api/chat',
}),
})
return (
<ChatSection handler={handler} className="flex h-full">
@@ -469,7 +470,7 @@ function CustomChat() {
}
```
You can also custom ArtifactCard for your artifact type.
You can also customize ArtifactCard for your artifact type:
```tsx
import { Image } from 'lucide-react'
@@ -484,37 +485,27 @@ function CustomArtifactCard({ data }: { data: Artifact }) {
/>
)
}
// update markdown annotation renderers to use your custom artifact card
<ChatMessage.Content>
<ChatMessage.Content.Markdown
annotationRenderers={{
artifact: CustomArtifactCard,
}}
/>
</ChatMessage.Content>
```
To trigger your custom artifact viewer, the AI response should include an annotation with the matching artifact type:
To trigger your custom artifact viewer, the AI response should include a part with the matching artifact type:
```tsx
// Example of how to create an artifact in AI response
const response = `Here is your image!
\`\`\`annotation
${JSON.stringify({
type: 'artifact',
data: {
type: 'image', // This matches your viewer's check
data: {
imageUrl: 'https://example.com/image.jpg',
caption: 'A beautiful landscape'
},
created_at: Date.now(),
},
})}
\`\`\`
`
```ts
message = {
parts: [
{
type: 'data-artifact',
data: {
type: 'code',
data: {
title: 'Data Visualization Script',
file_name: 'visualize_data.py',
language: 'python',
code: 'import matplotlib.pyplot as plt\n# Code...',
},
}
}
]
}
```
You can create multiple custom artifact viewers for different content types:
@@ -536,7 +527,7 @@ For a complete working example of custom artifact viewers, check out the demo im
- Custom `ImageArtifactViewer` implementation
- Integration with existing chat components
- Sample messages with artifact annotations
- Sample messages with artifact parts
- Copy-to-clipboard functionality for the code
### Canvas Auto-Show
@@ -547,7 +538,7 @@ The canvas automatically appears when artifacts are present:
// Canvas appears automatically when message contains artifacts
<ChatMessage message={messageWithArtifact}>
<ChatMessage.Content>
<ChatMessage.Content.Markdown />
<ChatMessage.Part.Artifact />
</ChatMessage.Content>
</ChatMessage>
```
@@ -732,4 +723,4 @@ function CopyArtifact() {
- [Examples](./examples.mdx) - See complete artifact implementations
- [Customization](./customization.mdx) - Style and customize artifact appearance
- [Widgets](./widgets.mdx) - Explore related widget functionality
- [Annotations](./annotations.mdx) - Understand the annotation system
- [Parts](./parts.mdx) - Understand the message parts system
+81 -27
View File
@@ -13,10 +13,14 @@ The `ChatSection` is the root component that provides context and layout for all
```tsx
import { ChatSection } from '@llamaindex/chat-ui'
import { useChat } from 'ai/react'
import { useChat } from '@ai-sdk/react'
function MyChat() {
const handler = useChat({ api: '/api/chat' })
const handler = useChat({
transport: new DefaultChatTransport({
api: '/api/chat',
}),
})
return <ChatSection handler={handler} />
}
```
@@ -34,6 +38,7 @@ interface ChatSectionProps {
- **handler**: Chat handler from `useChat` or custom implementation
- **className**: Custom CSS classes for styling
- **children**: Custom layout (defaults to `ChatMessages` + `ChatInput`)
- **autoOpenCanvas**: Automatically opens the `ChatCanvas` when artifacts are present
### Default Layout
@@ -119,7 +124,7 @@ Action buttons for the message list:
```tsx
<ChatMessages.Actions>
<button onClick={reload}>Reload</button>
<button onClick={regenerate}>Regenerate</button>
<button onClick={stop}>Stop</button>
</ChatMessages.Actions>
```
@@ -133,7 +138,7 @@ Action buttons for the message list:
## ChatMessage
Individual message component with full annotation support and role-based rendering.
Individual message component which renders, the avatar, the content and actions of a message.
### Basic Usage
@@ -147,7 +152,7 @@ function CustomMessage({ message, isLast }) {
<UserAvatar role={message.role} />
</ChatMessage.Avatar>
<ChatMessage.Content>
<ChatMessage.Content.Markdown />
<ChatMessage.Part.Markdown />
</ChatMessage.Content>
<ChatMessage.Actions />
</ChatMessage>
@@ -166,6 +171,18 @@ interface ChatMessageProps {
}
```
### Message Structure
The Message data structure stores the content of a message in so called parts:
```typescript
interface Message {
id: string
role: 'user' | 'assistant' | 'system'
parts: MessagePart[]
}
```
### Sub-components
#### ChatMessage.Avatar
@@ -182,17 +199,16 @@ User/assistant avatar display:
#### ChatMessage.Content
Main content area with annotation support:
This is the main content area which configures a couple of renders for specific message parts:
```tsx
<ChatMessage.Content isLoading={isLoading} append={append}>
<ChatMessage.Content.Markdown />
<ChatMessage.Content.Image />
<ChatMessage.Content.Source />
<ChatMessage.Content.Event />
<ChatMessage.Content.AgentEvent />
<ChatMessage.Content.DocumentFile />
<ChatMessage.Content.SuggestedQuestions />
<ChatMessage.Content>
<ChatMessage.Part.Markdown />
<ChatMessage.Part.Artifact />
<ChatMessage.Part.Sources />
<ChatMessage.Part.Event />
<ChatMessage.Part.File />
<ChatMessage.Part.Suggestion />
</ChatMessage.Content>
```
@@ -207,18 +223,16 @@ Message-level actions (copy, regenerate, etc.):
</ChatMessage.Actions>
```
### Content Types
### Message Parts
The content system supports multiple annotation types:
There are different renderers available for each part in the message:
- **Markdown** - Rich text with LaTeX support
- **Image** - Image display with preview
- **Artifact** - Interactive code/document editing
- **Source** - Citation and source links
- **Event** - Process events and status
- **AgentEvent** - Agent-specific events with progress
- **DocumentFile** - File attachments
- **SuggestedQuestions** - Follow-up question suggestions
- **TextPart** - Rich text with Markdown and LaTeX support
- **ArtifactPart** - Interactive code/document editing
- **SourcesPart** - Citation and source links
- **EventPart** - Process events and status updates
- **FilePart** - File attachments and uploads
- **SuggestedQuestionsPart** - Follow-up question suggestions
## ChatInput
@@ -363,7 +377,11 @@ function ChatWithCanvas() {
```tsx
function AdvancedChat() {
const handler = useChat({ api: '/api/chat' })
const handler = useChat({
transport: new DefaultChatTransport({
api: '/api/chat',
}),
})
return (
<ChatSection handler={handler} className="flex h-full">
@@ -406,16 +424,52 @@ All components have access to chat context through hooks:
import { useChatUI, useChatMessage } from '@llamaindex/chat-ui'
function CustomComponent() {
const { messages, isLoading, append } = useChatUI()
const { messages, status, sendMessage } = useChatUI()
const { message } = useChatMessage() // Only in message context
// Component logic
}
```
## Message Parts System
Each message can have multiple parts. Parts are rendered in the order they are received.
For more information on parts, see [Message Parts](./parts.mdx).
### Creating Custom Parts
```tsx
import { usePart } from '@llamaindex/chat-ui'
function CustomPart() {
const { data } = usePart()
return (
<div className="custom-part">
{/* Custom part rendering */}
</div>
)
}
```
### Backend Integration
Parts can be sent from the backend via SSE protocol:
```typescript
// Backend example
const parts = [
{ type: 'weather', data: weatherInfo },
{ type: 'sources', data: sourceNodes }
]
// Send as SSE
response.write(`data: ${JSON.stringify({ parts })}\n\n`)
```
## Next Steps
- [Widgets](./widgets.mdx) - Learn about specialized content widgets
- [Annotations](./annotations.mdx) - Implement rich content support
- [Message Parts](./message-parts.mdx) - Implement rich content support with parts
- [Hooks](./hooks.mdx) - Understand the hook system
- [Customization](./customization.mdx) - Style and theme the components
+10 -5
View File
@@ -51,6 +51,7 @@ function CustomStyledChat() {
return (
<ChatSection
handler={handler}
autoOpenCanvas={true}
className="bg-gradient-to-b from-blue-50 to-white"
>
<ChatMessages className="bg-white/80 backdrop-blur rounded-lg shadow-lg">
@@ -107,9 +108,9 @@ function CustomMessageLayout() {
<div className="flex-1">
<ChatMessage.Content>
<ChatMessage.Content.Markdown />
<ChatMessage.Content.Image />
<ChatMessage.Content.Source />
<ChatMessage.Part.Markdown />
<ChatMessage.Part.Image />
<ChatMessage.Part.Source />
</ChatMessage.Content>
<ChatMessage.Actions>
@@ -167,7 +168,7 @@ function RoleBasedMessage() {
<div className={`flex ${styles.container} mb-4`}>
<div className={`${styles.bubble} ${styles.maxWidth} p-4`}>
<ChatMessage.Content>
<ChatMessage.Content.Markdown />
<ChatMessage.Part.Markdown />
</ChatMessage.Content>
</div>
</div>
@@ -513,7 +514,11 @@ export const useTheme = () => useContext(ThemeContext)
```tsx
function ThemedChatSection() {
const { theme } = useTheme()
const handler = useChat({ api: '/api/chat' })
const handler = useChat({
transport: new DefaultChatTransport({
api: '/api/chat',
}),
})
const themeClasses = {
light: 'bg-white text-gray-900',
+22 -13
View File
@@ -121,7 +121,7 @@ The `markdown.css` file includes styling for code blocks using [highlight.js](ht
### 1. Create a Chat API Route
Set up an API route to handle chat requests. Here's an example using Next.js:
Set up an API route to handle chat requests. Here's an example using Next.js with LlamaIndex:
```typescript
// app/api/chat/route.ts
@@ -130,13 +130,14 @@ import { NextResponse } from 'next/server'
export async function POST(request: Request) {
const { messages } = await request.json()
// Your chat logic here
// Your LlamaIndex chat logic here
const response = await generateChatResponse(messages)
// Return streaming response in LlamaIndex format
return new Response(response, {
headers: {
'Content-Type': 'text/plain; charset=utf-8',
'X-Vercel-AI-Data-Stream': 'v1',
'Content-Type': 'text/event-stream',
'Connection': 'keep-alive',
},
})
}
@@ -144,17 +145,21 @@ export async function POST(request: Request) {
### 2. Create Your Chat Component
The easiest way to get started is to connect the whole `ChatSection` component with `useChat` hook from [vercel/ai](https://github.com/vercel/ai):
The easiest way to get started is to connect the whole `ChatSection` component with `useChat` hook from `@ai-sdk/react`:
```tsx
'use client'
import { ChatSection } from '@llamaindex/chat-ui'
import { useChat } from 'ai/react'
import { useChat } from '@ai-sdk/react'
export default function Chat() {
const handler = useChat({
api: '/api/chat',
// use transport to specify the chat API endpoint
// https://ai-sdk.dev/docs/migration-guides/migration-guide-5-0#chat-transport-architecture
transport: new DefaultChatTransport({
api: '/api/chat',
}),
})
return (
@@ -172,7 +177,7 @@ Components are designed to be composable. You can use them as is with the simple
```tsx
import { ChatSection, ChatMessages, ChatInput } from '@llamaindex/chat-ui'
import LlamaCloudSelector from './components/LlamaCloudSelector' // your custom component
import { useChat } from 'ai/react'
import { useChat } from '@ai-sdk/react'
const ChatExample = () => {
const handler = useChat()
@@ -234,7 +239,7 @@ Additionally, you can also override each component's styles by setting custom cl
```tsx
import { ChatSection, ChatMessages, ChatInput } from '@llamaindex/chat-ui'
import { useChat } from 'ai/react'
import { useChat } from '@ai-sdk/react'
const ChatExample = () => {
const handler = useChat()
@@ -269,7 +274,11 @@ import {
} from '@llamaindex/chat-ui'
function CustomChat() {
const handler = useChat({ api: '/api/chat' })
const handler = useChat({
transport: new DefaultChatTransport({
api: '/api/chat',
}),
})
return (
<ChatSection handler={handler} className="flex-row gap-4">
@@ -290,7 +299,7 @@ Provide initial context or welcome messages:
```tsx
const handler = useChat({
api: '/api/chat',
initialMessages: [
messages: [
{
id: '1',
role: 'assistant',
@@ -309,7 +318,7 @@ For any language that the LLM generates, you can specify a custom renderer to re
Now that you have a basic chat interface running:
1. **Explore Components** - Learn about [Core Components](./core-components.mdx) for customization
2. **Add Rich Content** - Implement [Annotations](./annotations.mdx) for images, files, and sources
2. **Add Rich Content** - Implement [Parts](./parts.mdx) for images, files, and sources
3. **Enable Artifacts** - Set up [Artifacts](./artifacts.mdx) for interactive code and documents
4. **Customize Styling** - Read the [Customization](./customization.mdx) guide for theming
@@ -323,7 +332,7 @@ Now that you have a basic chat interface running:
**Build errors**: Check that your bundler supports the package's export conditions.
**Chat not working**: Verify your API route is returning the correct response format for the Vercel AI SDK.
**Chat not working**: Verify your API route is returning the correct response format for the LlamaIndex streaming protocol.
### Getting Help
+105 -37
View File
@@ -20,9 +20,8 @@ function CustomChatComponent() {
input,
setInput,
isLoading,
error,
append,
reload,
sendMessage,
regenerate,
stop,
setMessages,
requestData,
@@ -30,9 +29,10 @@ function CustomChatComponent() {
} = useChatUI()
const handleSendMessage = async () => {
await append({
await sendMessage({
id: 'user-msg-1',
role: 'user',
content: input,
parts: [{ type: 'text', text: input }],
})
}
@@ -48,14 +48,14 @@ function CustomChatComponent() {
**Returned Properties:**
- `messages` - Array of chat messages
- `messages` - Array of chat messages with parts
- `input` - Current input value
- `setInput` - Function to update input
- `isLoading` - Loading state boolean
- `error` - Error object if any
- `append` - Function to add message
- `reload` - Function to reload last message
- `isLoading` - Loading state boolean (computed from status)
- `status` - Current chat status ('submitted' | 'streaming' | 'ready' | 'error')
- `sendMessage` - Function to send a message
- `stop` - Function to stop current generation
- `regenerate` - Function to regenerate a message
- `setMessages` - Function to update message array
- `requestData` - Additional request data
- `setRequestData` - Function to update request data
@@ -73,11 +73,14 @@ function CustomMessageContent() {
return (
<div>
<p>Role: {message.role}</p>
<p>Content: {message.content}</p>
<p>Parts: {message.parts.length}</p>
<p>Is last message: {isLast ? 'Yes' : 'No'}</p>
{message.annotations && (
<p>Has annotations: {message.annotations.length}</p>
)}
{message.parts.map((part, index) => (
<div key={index}>
<p>Part {index + 1}: {part.type}</p>
</div>
))}
</div>
)
}
@@ -85,9 +88,63 @@ function CustomMessageContent() {
**Returned Properties:**
- `message` - Current message object
- `message` - Current message object with parts array
- `isLast` - Boolean indicating if this is the last message
### usePart
Access the current message content within part components. This hook provides type-safe access to specific part types in the current message.
```tsx
import { usePart } from '@llamaindex/chat-ui'
function TextPartComponent() {
const textPart = usePart('text')
if (!textPart) return null
return <p>{textPart.text}</p>
}
function ArtifactPartComponent() {
const artifactPart = usePart('data-artifact')
if (!artifactPart) return null
return (
<div>
<h4>{artifactPart.title}</h4>
<p>Type: {artifactPart.data.type}</p>
</div>
)
}
function CustomPartComponent() {
const customPart = usePart<CustomPartType>('data-custom-type')
if (!customPart) return null
return <CustomRenderer data={customPart.data} />
}
```
**Function Overloads:**
The hook provides automatic type inference for built-in part types:
- `usePart('text')` → `TextPart | null`
- `usePart('data-file')` → `FilePart | null`
- `usePart('data-artifact')` → `ArtifactPart | null`
- `usePart('data-event')` → `EventPart | null`
- `usePart('data-sources')` → `SourcesPart | null`
- `usePart('data-suggestion')` → `SuggestionPart | null`
**Usage Notes:**
- Must be used within a `ChatPartProvider` context
- Returns `null` if the part type doesn't match the current part
- For custom part types, use the generic parameter: `usePart<CustomPart>('custom-type')`
### useChatInput
Access input form state and handlers.
@@ -136,21 +193,34 @@ Access messages list state and handlers.
import { useChatMessages } from '@llamaindex/chat-ui'
function CustomMessageList() {
const { messages, isLoading, reload, stop, isEmpty, scrollToBottom } =
const { messages, isLoading, regenerate, stop, isEmpty, scrollToBottom } =
useChatMessages()
return (
<div>
<div className="messages">
{messages.map((msg, i) => (
<div key={i}>{msg.content}</div>
<div key={i}>
<p>Role: {msg.role}</p>
<div>
{msg.parts.map((part, index) => (
<div key={index}>
{part.type === 'text' ? (
<p>{part.text}</p>
) : (
<p>{JSON.stringify(part.data)}</p>
)}
</div>
))}
</div>
</div>
))}
</div>
{isEmpty && <p>No messages yet</p>}
<button onClick={reload} disabled={isLoading}>
Reload
<button onClick={regenerate} disabled={isLoading}>
Regenerate
</button>
<button onClick={stop}>Stop</button>
<button onClick={scrollToBottom}>Scroll to Bottom</button>
@@ -163,7 +233,7 @@ function CustomMessageList() {
- `messages` - Array of messages
- `isLoading` - Loading state
- `reload` - Reload last message
- `regenerate` - Regenerate last message
- `stop` - Stop generation
- `isEmpty` - Boolean if no messages
- `scrollToBottom` - Function to scroll to bottom
@@ -326,13 +396,11 @@ function useCustomChat() {
const messages = useChatMessages()
const sendMessageWithMetadata = async (content: string, metadata: any) => {
await chatUI.append(
{
role: 'user',
content,
},
{ data: metadata }
)
await chatUI.sendMessage({
id: `user-${Date.now()}`,
role: 'user',
parts: [{ type: 'text', text: content }]
}, { data: metadata })
}
const getLastAssistantMessage = () => {
@@ -398,7 +466,7 @@ function SafeHookUsage() {
## Next Steps
- [Annotations](./annotations.mdx) - Learn how hooks work with annotation data
- [Parts](./parts.mdx) - Learn how hooks work with message parts
- [Customization](./customization.mdx) - Use hooks for custom styling and behavior
- [Examples](./examples.mdx) - See complete examples using hooks
- [Core Components](./core-components.mdx) - Understand component-hook relationships
@@ -620,10 +688,10 @@ function WorkflowChatApp() {
>
<ChatMessage.Avatar />
<ChatMessage.Content>
<ChatMessage.Content.Markdown />
<ChatMessage.Content.Source />
{/* Custom annotations for UIEvents */}
<WeatherAnnotation />
<ChatMessage.Part.Markdown />
<ChatMessage.Part.Source />
{/* Renderer for custom message parts */}
<WeatherPart />
</ChatMessage.Content>
<ChatMessage.Actions />
</ChatMessage>
@@ -664,7 +732,7 @@ def handle_start_event(ev: StartEvent) -> MyNextEvent:
#### Built-in Workflow Events
The hook automatically processes workflow events (using `useWorkflow`) and renders them as annotations in the chat interface, making it easy to build rich conversational experiences with LlamaDeploy workflows.
The hook automatically processes workflow events (using `useWorkflow`) and renders them as parts in the chat interface, making it easy to build rich conversational experiences with LlamaDeploy workflows.
Your LlamaDeploy workflows can send three main types of events to enhance the chat experience:
##### 1. SourceNodesEvent - Citations and References
@@ -693,7 +761,7 @@ ctx.write_event_to_stream(
)
```
> Note: Your `ChatMessage.Content` component needs to have the `ChatMessage.Content.Source` component as a child to display the citations and references (as shown in the example above).
> Note: Your `ChatMessage.Content` component needs to have the `ChatMessage.Part.Source` component as a child to display the citations and references (as shown in the example above).
##### 2. ArtifactEvent - Code and Artifacts
@@ -720,11 +788,11 @@ ctx.write_event_to_stream(
)
```
> Note: Your `ChatMessage.Content` component needs to have the `ChatMessage.Content.Markdown` component as a child to display the artifacts inline in the markdown component (as shown in the example above).
> Note: Your `ChatMessage.Content` component needs to have the `ChatMessage.Part.Markdown` component as a child to display the artifacts inline in the markdown component (as shown in the example above).
##### 3. UIEvent - Custom UI Components
Send custom data to render it in a specialized UI components. In your workflow code, you can send `UIEvent`s for this - for example to render a weather annotation in the chat interface:
Send custom data to render it in a specialized UI components. In your workflow code, you can send `UIEvent`s for this - for example to render a weather part in the chat interface:
```python
from llama_index.core.chat_ui.events import (
@@ -752,4 +820,4 @@ ctx.write_event_to_stream(
)
```
To render this custom UI component, you need to add it as child to your `ChatMessage.Content` component. The example above will render the [`WeatherAnnotation`](../../examples/llamadeploy/chat/ui/components/custom/custom-weather.tsx) component in the chat interface.
To render this custom UI component, you need to add it as child to your `ChatMessage.Content` component. The example above will render the [`WeatherPart`](../../examples/llamadeploy/chat/ui/components/custom/custom-weather.tsx) component in the chat interface.
+5 -5
View File
@@ -8,7 +8,7 @@ LlamaIndex Chat UI is a comprehensive React component library designed for build
## Key Features
- **Complete Chat Interface** - Full-featured chat components with message history, input, and OpenAI-style canvas
- **Rich Annotations** - Support for images, files, sources, events, and custom annotations
- **Rich Parts** - Support for images, files, sources, events, and custom parts
- **Interactive Artifacts** - Code and document artifacts with editing and version management
- **File Upload Support** - Built-in handling for multiple file types (PDF, images, documents)
- **Beautiful** - Built on shadcn/ui for beautiful UI
@@ -29,7 +29,7 @@ LlamaIndex Chat UI is a comprehensive React component library designed for build
The library includes a comprehensive widget system for handling various content types:
- **Content Widgets** - Markdown, code blocks, image display
- **Annotation Widgets** - Sources, events, suggested questions
- **Widgets for Part Rendering** - Sources, events, suggested questions
- **Interactive Widgets** - File upload, document editing, code editing
## Getting Started
@@ -46,7 +46,7 @@ For more information on configuration, please see detailed guide in [Getting Sta
```tsx
import { ChatSection } from '@llamaindex/chat-ui'
import { useChat } from 'ai/react'
import { useChat } from '@ai-sdk/react'
export default function MyChat() {
const handler = useChat({
@@ -62,7 +62,7 @@ This creates a complete chat interface with:
- Message history display
- User input with file upload
- Loading states and error handling
- Support for rich content and annotations
- Support for rich content using renderers for message parts
## Architecture
@@ -71,7 +71,7 @@ The library follows a composable architecture where you can:
1. **Use the complete solution** - `ChatSection` provides everything out of the box
2. **Compose custom layouts** - Mix and match components for custom designs
3. **Extend with widgets** - Add specialized content handling
4. **Create custom annotations** - Build domain-specific content types
4. **Create custom parts** - Build domain-specific content types
## Integration
+1 -1
View File
@@ -9,7 +9,7 @@
"core-components",
"widgets",
"hooks",
"annotations",
"parts",
"artifacts",
"customization",
"examples"
+515
View File
@@ -0,0 +1,515 @@
---
title: Message Parts
description: Working with rich content parts for multimedia and interactive chat experiences
---
Message parts are the building blocks for creating rich, interactive chat experiences beyond simple text. They allow you to embed text, files, sources, events, artifacts, and custom content types directly into chat messages as structured components.
## Parts System Overview
Chat-UI supports two fundamental types of parts that make up chat messages:
### 1. Text Parts
Text parts contain markdown content that gets rendered as formatted text. They use the `text` type and are the primary way to display textual content.
### 2. Data Parts
Data parts contain structured data for rich interactive components like weather widgets, file attachments, sources, and more.
Both built-in and custom parts are both using the `data-` prefix. We're using here the convention from Vercel AI SDK 5 to use the `data-` prefix to detect data parts in messages.
## Message Structure with Parts
```typescript
interface Message {
id: string
role: 'user' | 'assistant' | 'system'
parts: MessagePart[]
}
// Two types of parts
type MessagePart = TextPart | DataPart
// Text parts for markdown content
interface TextPart {
type: 'text'
text: string
}
// Data parts for rich components
interface DataPart {
id?: string // if provided, only the last part with same id is kept
type: string // should use 'data-' prefix for data parts
data?: any
}
```
## How Chat-UI Renders Parts
Parts are automatically rendered when using the `ChatMessage.Content` component. Each part type has a corresponding component that checks if the current part matches its type:
```tsx
<ChatMessage message={message}>
<ChatMessage.Content>
{/* Built-in part components */}
<ChatMessage.Part.Markdown />
<ChatMessage.Part.File />
<ChatMessage.Part.Event />
<ChatMessage.Part.Artifact />
<ChatMessage.Part.Source />
<ChatMessage.Part.Suggestion />
{/* Custom part components */}
<WeatherPart />
<WikiPart />
</ChatMessage.Content>
</ChatMessage>
```
The rendering system:
1. Iterates through each part in `message.parts`
2. Provides each part to all child components via `ChatPartProvider`
3. Each component uses `usePart(partType)` to check if it should render
4. Only the matching component renders, others return `null`
## Built-in Parts
Chat-UI provides several built-in part types for common use cases:
### Text Parts (`text`)
Display markdown content with syntax highlighting, links, and formatting.
```typescript
const textPart = {
type: 'text',
text: `
# Heading
This is **bold** and *italic* text.
\`\`\`javascript
console.log('Hello, world!')
\`\`\`
`
}
```
### File Parts (`data-file`)
Display file attachments with download links and metadata.
```typescript
const filePart = {
type: 'data-file',
data: {
name: 'quarterly-report.pdf',
type: 'application/pdf',
url: '/files/quarterly-report.pdf',
size: 2048576 // bytes
}
}
```
### Source Parts (`data-sources`)
Display citations and source references with document grouping.
```typescript
const sourcesPart = {
type: 'data-sources',
data: {
nodes: [
{
id: 'source1',
url: '/documents/research-paper.pdf',
metadata: {
title: 'Machine Learning in Healthcare',
author: 'Dr. Jane Smith',
page_number: 15,
section: 'Methodology'
}
}
]
}
}
```
### Event Parts (`data-event`)
Display process events, function calls, and system activities with status updates.
```typescript
const eventPart = {
id: 'search_event', // Same ID will update previous event
type: 'data-event',
data: {
title: 'Calling tool `search_database`',
status: 'success',
data: {
query: 'machine learning papers',
result: 'Found 8 relevant papers'
}
}
}
```
### Artifact Parts (`data-artifact`)
Create interactive code and document artifacts that users can edit.
```typescript
const artifactPart = {
type: 'data-artifact',
data: {
type: 'code',
data: {
title: 'Data Analysis Script',
file_name: 'analyze_data.py',
language: 'python',
code: `
import pandas as pd
import matplotlib.pyplot as plt
def analyze_sales_data(file_path):
df = pd.read_csv(file_path)
monthly_sales = df.groupby('month')['sales'].sum()
return monthly_sales
`
}
}
}
```
### Suggestion Parts (`data-suggested_questions`)
Provide interactive follow-up questions to guide conversation.
```typescript
const suggestionPart = {
type: 'data-suggested_questions',
data: [
'Can you explain the methodology in more detail?',
'What are the potential limitations?',
'How does this compare to traditional methods?'
]
}
```
## Creating Custom Parts
Create domain-specific parts for specialized content by implementing a custom render component:
### 1. Define the Part Type and Data Interface
```typescript
const WeatherPartType = 'data-weather'
type WeatherData = {
location: string
temperature: number
condition: string
humidity: number
windSpeed: number
}
```
### 2. Create the Component
```tsx
import { usePart } from '@llamaindex/chat-ui'
export function WeatherPart() {
// usePart returns data only if current part matches the type
const weatherData = usePart<WeatherData>(WeatherPartType)
if (!weatherData) return null
return (
<div className="weather-widget">
<h3>{weatherData.location}</h3>
<div className="temperature">{weatherData.temperature}°C</div>
<div className="condition">{weatherData.condition}</div>
<div className="details">
<span>Humidity: {weatherData.humidity}%</span>
<span>Wind: {weatherData.windSpeed} km/h</span>
</div>
</div>
)
}
```
### 3. Add to Message Rendering
```tsx
<ChatMessage message={message}>
<ChatMessage.Content>
<ChatMessage.Part.Markdown />
<ChatMessage.Part.File />
{/* Add your custom component */}
<WeatherPart />
</ChatMessage.Content>
</ChatMessage>
```
## Adding Parts from Backend via SSE Protocol
Parts are streamed using the **Server-Sent Events (SSE)** protocol, which provides real-time communication between the server and client.
Read more about SSE protocol in [Vercel AI SDK 5](https://ai-sdk.dev/docs/migration-guides/migration-guide-5-0#proprietary-protocol---server-sent-events) documentation.
Here's how the streaming implementation works in the backend:
### Response Headers
The server must set specific headers for SSE streaming:
```typescript
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Connection': 'keep-alive',
},
})
```
### Stream Format
Each chunk sent to the client must follow the SSE format with a `data:` prefix:
```typescript
const DATA_PREFIX = 'data: '
function writeStream(chunk: TextChunk | DataChunk) {
controller.enqueue(
encoder.encode(`${DATA_PREFIX}${JSON.stringify(chunk)}\n\n`)
)
}
```
### Chunk Types
The streaming protocol supports two types of chunks:
#### Text Chunks (for streaming text content)
```typescript
interface TextChunk {
type: 'text-start' | 'text-delta' | 'text-end'
id: string
delta?: string // only for text-delta
}
// Example sequence:
// data: {"type":"text-start","id":"msg-123"}
// data: {"type":"text-delta","id":"msg-123","delta":"Hello "}
// data: {"type":"text-delta","id":"msg-123","delta":"world!"}
// data: {"type":"text-end","id":"msg-123"}
```
#### Data Chunks (for rich components)
```typescript
interface DataChunk {
id?: string // optional - same ID replaces previous parts
type: `data-${string}` // requires 'data-' prefix
data: Record<string, any>
}
// Example:
// data: {"type":"data-weather","data":{"location":"SF","temp":22}}
```
### Implementation Example
```typescript
const fakeChatStream = (parts: (string | MessagePart)[]): ReadableStream => {
return new ReadableStream({
async start(controller) {
const encoder = new TextEncoder()
function writeStream(chunk: TextChunk | DataChunk) {
controller.enqueue(
encoder.encode(`${DATA_PREFIX}${JSON.stringify(chunk)}\n\n`)
)
}
async function writeText(content: string) {
const messageId = crypto.randomUUID()
// Start text stream
writeStream({ id: messageId, type: 'text-start' })
// Stream tokens
for (const token of content.split(' ')) {
writeStream({
id: messageId,
type: 'text-delta',
delta: token + ' '
})
await new Promise(resolve => setTimeout(resolve, 30))
}
// End text stream
writeStream({ id: messageId, type: 'text-end' })
}
async function writeData(data: MessagePart) {
writeStream({
id: data.id,
type: `data-${data.type}`,
data: data.data
})
}
// Stream all parts
for (const item of parts) {
if (typeof item === 'string') {
await writeText(item)
} else {
await writeData(item)
}
}
controller.close()
},
})
}
```
### Important ID Behavior for Data Parts
When data parts have the same `id`, only the **last** data part with that ID will exist in `message.parts`. This is useful for:
- **Single data display**: Show only the final result (e.g., hide loading, show final weather data)
- **Progressive updates**: Update the same component as new data arrives (e.g., streaming events)
If you want multiple parts of the same type, **don't provide an ID** or use different IDs.
Example:
1. When calling a tool, send an event with tool call information:
```typescript
part1 = {
id: 'demo_sample_event_id',
type: 'data-event',
data: {
title: 'Calling tool `get_weather` with input `San Francisco, CA`',
status: 'pending',
},
}
```
2. When the tool call is completed, send an event with the tool call result. The previous event with the same id will be replaced by the new one.
```typescript
part2 = {
id: 'demo_sample_event_id',
type: 'data-event',
data: {
title: 'Calling tool `get_weather` with input `San Francisco, CA`',
status: 'pending',
},
}
```
When checking `message.parts`, you will only see the last event with the final result.
### Important Notes
- **SSE Format**: Each message must be prefixed with `data: ` and end with `\n\n`
- **JSON Encoding**: All chunks are JSON-encoded objects
- **Text Streaming**: Text content requires start/delta/end sequence for proper rendering
- **Data Parts**: Must use `data-` prefix in the type field
- **ID Behavior**: Same IDs in data parts will replace previous parts with that ID
## Complete Message Example
```typescript
const message = {
id: 'msg-123',
role: 'assistant',
parts: [
{
type: 'text',
text: 'I\'ve analyzed your data and here are the results:'
},
{
type: 'data-artifact',
data: {
type: 'code',
data: {
title: 'Sales Analysis',
file_name: 'analysis.py',
language: 'python',
code: 'import pandas as pd\n# Analysis code...'
}
}
},
{
type: 'data-sources',
data: {
nodes: [
{
id: '1',
url: '/data/sales.csv',
metadata: { title: 'Sales Data Q4 2024' }
}
]
}
},
{
type: 'data-suggested_questions',
data: [
'Can you explain the quarterly trends?',
'What about the seasonal patterns?',
'How can we improve performance?'
]
}
]
}
```
## Utility Functions
### usePart Hook
Extract part data by type within part components:
```tsx
import { usePart } from '@llamaindex/chat-ui'
function CustomPartComponent() {
// Returns data only if current part matches type, null otherwise
const weatherData = usePart<WeatherData>('data-weather')
const textContent = usePart<string>('text')
// Component logic...
}
```
### getParts Function
Extract all parts of a specific type from a message:
```tsx
import { getParts } from '@llamaindex/chat-ui'
// Get all text content from a message
const allTextParts = getParts<string>(message, 'text')
// Get all weather data parts
const allWeatherData = getParts<WeatherData>(message, 'data-weather')
```
This function is useful for:
- Aggregating data from multiple parts
- Building summaries or indexes
- Processing historical data
## Best Practices
1. **Use the `data-` prefix** for all custom part types
2. **Provide IDs** only when you want parts to replace each other
3. **Keep data structures simple** and serializable
4. **Handle null cases** in custom components when data doesn't match
5. **Mix text and data parts** to create rich, contextual experiences
6. **Stream progressively** to improve perceived performance
## Next Steps
- [Artifacts](./artifacts.mdx) - Learn about interactive code and document artifacts
- [Widgets](./widgets.mdx) - Explore widget implementation details
- [Examples](./examples.mdx) - See complete implementation examples
- [Customization](./customization.mdx) - Style and customize part appearance
+41 -103
View File
@@ -3,13 +3,13 @@ title: Widgets
description: Comprehensive guide to specialized content widgets for rich chat experiences
---
Widgets are specialized components for displaying and interacting with rich content in chat messages. They provide functionality beyond simple text, enabling multimedia, interactive elements, and custom annotations.
Widgets are specialized components for displaying and interacting with rich content in chat messages. They provide functionality beyond simple text, enabling multimedia, interactive elements, and custom parts.
This section describes how to use them standalone.
## Content Widgets
## ChatUI Widgets
### Markdown
## Markdown
Renders rich text with LaTeX math support, syntax highlighting, and citations.
@@ -155,70 +155,26 @@ export default function Home() {
- **Image Support** - Embed images
- **Live Preview** - Real-time markdown preview
## Annotation Widgets
### ChatFile
Used for rendering additional rich content in a chat message. See [Annotations](./annotations.mdx) for more information on how to add annotations to a message.
### ChatImage
Displays images with preview and zoom functionality.
Displays a file attachment like image, pdf, etc.
```tsx
import { ChatImage } from '@llamaindex/chat-ui/widgets'
function ImageDisplay() {
return (
<ChatImage
data={{
url: 'https://example.com/image.jpg',
alt: 'Description of the image',
}}
/>
)
}
```
**Features:**
- **Zoom & Pan** - Interactive image viewing
- **Lazy Loading** - Performance optimization
- **Alt Text** - Accessibility support
- **Error Handling** - Graceful fallback for broken images
### ChatFiles
Displays file attachments with download and preview.
```tsx
import { ChatFiles } from '@llamaindex/chat-ui/widgets'
import { ChatFile } from '@llamaindex/chat-ui/widgets'
function FileDisplay() {
return (
<ChatFiles
data={{
files: [
{
id: '1',
name: 'report.pdf',
type: 'application/pdf',
url: '/files/report.pdf',
size: 1024000,
},
],
<ChatFile
file={{
filename: 'upload.pdf',
mediaType: 'application/pdf',
url: 'https://pdfobject.com/pdf/sample.pdf',
}}
/>
)
}
```
**Supported File Types:**
- **PDF** - Inline viewer
- **Images** - Thumbnail preview
- **Text Files** - Content preview
- **CSV** - Data table preview
- **Word Documents** - Document preview
### ChatSources
Displays source citations with document grouping.
@@ -253,52 +209,36 @@ function SourceDisplay() {
- **Click to View** - Opens source documents
- **Metadata Display** - Shows title, author, date
### ChatEvents
### ChatEvent
Displays collapsible process events and status updates.
Displays collapsible process event with status updates.
```tsx
import { ChatEvents } from '@llamaindex/chat-ui/widgets'
import { ChatEvent } from '@llamaindex/chat-ui/widgets'
function EventDisplay() {
// When Event with loading status
function SearchDocumentsEvent() {
return (
<ChatEvents
data={[
{
type: 'function_call',
name: 'search_documents',
args: { query: 'machine learning' },
result: 'Found 15 relevant documents',
},
]}
showLoading={false}
<ChatEvent
event={{
title: 'Searching documents',
description: 'Searching documents for machine learning',
status: 'pending',
}}
/>
)
}
```
### ChatAgentEvents
Displays agent-specific events with progress tracking.
```tsx
import { ChatAgentEvents } from '@llamaindex/chat-ui/widgets'
function AgentEventDisplay() {
// When Event with success status
function SearchDocumentsResult() {
return (
<ChatAgentEvents
data={{
agent_name: 'Research Assistant',
progress: 75,
current_step: 'Analyzing documents',
events: [
{ step: 'Searching', status: 'completed' },
{ step: 'Analyzing', status: 'in_progress' },
{ step: 'Summarizing', status: 'pending' },
],
<ChatEvent
event={{
title: 'Searching documents',
description: 'Searching documents for machine learning',
status: 'success',
data: 'Document content...',
}}
isFinished={true}
isLast={false}
/>
)
}
@@ -311,7 +251,7 @@ Interactive follow-up question suggestions.
```tsx
import { SuggestedQuestions } from '@llamaindex/chat-ui/widgets'
function QuestionSuggestions({ append, requestData }) {
function QuestionSuggestions({ regenerate, requestData }) {
return (
<SuggestedQuestions
questions={[
@@ -319,7 +259,7 @@ function QuestionSuggestions({ append, requestData }) {
'What are the practical applications?',
'How does this compare to other approaches?',
]}
append={append}
regenerate={regenerate}
requestData={requestData}
/>
)
@@ -350,8 +290,6 @@ function ChatStarters() {
}
```
## Utility Widgets
### FileUploader
Drag-and-drop file upload with validation.
@@ -427,7 +365,7 @@ function DocInfo({ document }) {
}
```
### Citation
## Citation
Individual citation component with linking.
@@ -451,7 +389,7 @@ function CitationLink({ source, index }) {
### Automatic Rendering
Annotation widgets render based on message annotations through dedicated annotation components:
Other widgets render based on message parts through dedicated components:
```tsx
import { ChatMessage } from '@llamaindex/chat-ui'
@@ -460,17 +398,17 @@ function MessageWithWidgets({ message }) {
return (
<ChatMessage message={message}>
<ChatMessage.Content>
<ChatMessage.Content.Markdown />
<ChatMessage.Content.Image /> {/* Renders ChatImage */}
<ChatMessage.Content.Source /> {/* Renders ChatSources */}
<ChatMessage.Content.Event /> {/* Renders ChatEvents */}
<ChatMessage.Part.Markdown />
<ChatMessage.Part.Image /> {/* Renders ChatImage */}
<ChatMessage.Part.Source /> {/* Renders ChatSources */}
<ChatMessage.Part.Event /> {/* Renders ChatEvents */}
</ChatMessage.Content>
</ChatMessage>
)
}
```
The `ChatMessage.Content.*` components internally use the annotation pattern described in [Annotations](./annotations.mdx), extracting data with `getAnnotationData` and passing it to the respective widgets.
The `ChatMessage.Part.*` components internally use the parts pattern described in [Parts](./parts.mdx), extracting data with `usePart` and passing it to the respective widgets.
### Manual Widget Usage
@@ -506,7 +444,7 @@ function CustomMessageLayout({ message }) {
Create custom widgets by following this pattern:
```tsx
interface WeatherData {
type WeatherData = {
location: string
temperature: number
condition: string
@@ -542,7 +480,7 @@ function App() {
## Next Steps
- [Annotations](./annotations.mdx) - Learn how to create and send annotation data
- [Parts](./parts.mdx) - Learn how to create and send parts
- [Artifacts](./artifacts.mdx) - Implement interactive code and document artifacts
- [Hooks](./hooks.mdx) - Understand the widget hook system
- [Customization](./customization.mdx) - Style and customize widget appearance
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@llamaindex/chat-ui-docs",
"version": "0.0.8",
"version": "0.1.0",
"files": [
"chat-ui"
],
+118 -36
View File
@@ -1,7 +1,6 @@
from fastapi import APIRouter, Request
from fastapi.responses import StreamingResponse
from app.vercel import VercelStreamResponse
from .vercel import SSEStreamResponse, get_text
router = APIRouter(prefix="/chat")
@@ -10,49 +9,92 @@ router = APIRouter(prefix="/chat")
async def chat(request: Request) -> StreamingResponse:
data = await request.json()
messages = data.get("messages", [])
last_message = messages[-1] if messages else {"content": ""}
last_message = messages[-1] if messages else {}
content = get_text(last_message)
query_text = f'User query: "{last_message.get("content", "")}".\\n'
query_text = f'User query: "{content}".\n'
sample_text = """
Welcome to the demo of @llamaindex/chat-ui. Let me show you the different types of components that can be triggered from the server.
# Advanced sample parts matching the Next.js advanced route
sample_parts = [
"Welcome to the demo of @llamaindex/chat-ui. Let me show you the different types of components that can be triggered from the server.",
"""
### Text Part
Text part is used to display text in the chat. It is in markdown format.
You can use markdown syntax to format the text. Some examples:
### Markdown with code block
- **bold** -> this is bold text
- *italic* -> this is italic text
- [link](https://www.google.com) -> this is a link
You can also display a code block inside markdown.
```js
const a = 1
const b = 2
const c = a + b
console.log(c)
```
```""",
### Annotations
"""
### Parts
"""
text_tokens = sample_text.split(' ')
sample_annotations = [
Beside text, you can also display parts in the chat. Parts can be displayed before or after the text.
**Built-in parts**
@llamaindex/chat-ui provides some built-in parts for you to use
- **file** -> display a file with name and url
- **event** -> display a event with title, status, and data
- **artifact** -> display a code artifact
- **sources** -> display a list of sources
- **suggested_questions** -> display a list of suggested questions
**Custom parts**
You can also create your own custom parts.
- **weather** -> display a weather card
- **wiki** -> display a wiki card
""",
"**file**: Here is the demo of a file part",
{
"type": "sources",
"type": "file",
"data": {
"nodes": [
{"id": "1", "url": "/sample.pdf"},
{"id": "2", "url": "/sample.pdf"},
],
},
"filename": "upload.pdf",
"mediaType": "application/pdf",
"url": "https://pdfobject.com/pdf/sample.pdf"
}
},
"**event**: Here is the demo of event parts. The second event part will override the first one because they have the same id",
{
"id": "demo_sample_event_id",
"type": "event",
"data": {
"title": "Calling tool `get_weather` with input `San Francisco, CA`",
"status": "pending"
}
},
{
"type": "artifact",
"id": "demo_sample_event_id", # Same id to override previous part
"type": "event",
"data": {
"type": "code",
"title": "Got response from tool `get_weather` with input `San Francisco, CA`",
"status": "success",
"data": {
"file_name": "sample.ts",
"language": "typescript",
"code": 'console.log("Hello, world!");',
},
},
"location": "San Francisco, CA",
"temperature": 22,
"condition": "sunny",
"humidity": 65,
"windSpeed": 12
}
}
},
"**weather**: Here is the demo of a weather part. It is a custom part",
{
"type": "weather",
"data": {
@@ -60,15 +102,55 @@ console.log(c)
"temperature": 22,
"condition": "sunny",
"humidity": 65,
"windSpeed": 12,
},
"windSpeed": 12
}
},
]
events = [
query_text,
*[f"{token} " for token in text_tokens],
*sample_annotations,
"**wiki**: Here is the demo of a wiki part",
{
"type": "wiki",
"data": {
"title": "LlamaIndex",
"summary": "LlamaIndex is a framework for building AI applications.",
"url": "https://www.llamaindex.ai",
"category": "AI",
"lastUpdated": "2025-06-02"
}
},
"**artifact**: Here is the demo of a artifact part",
{
"type": "artifact",
"data": {
"type": "code",
"data": {
"file_name": "code.py",
"code": 'print("Hello, world!")',
"language": "python"
}
}
},
"**sources**: Here is the demo of a sources part",
{
"type": "sources",
"data": {
"nodes": [
{"id": "1", "url": "/sample.pdf"},
{"id": "2", "url": "/sample.pdf"}
]
}
},
"**suggested_questions**: Here is the demo of a suggested_questions part",
{
"type": "suggested_questions",
"data": [
"I think you should go to the beach",
"I think you should go to the mountains",
"I think you should go to the city"
]
}
]
return VercelStreamResponse(events=events)
return SSEStreamResponse(parts=sample_parts, query=query_text)
+73 -45
View File
@@ -1,58 +1,86 @@
import asyncio
import json
from typing import Any, AsyncGenerator, Iterable, Union
import uuid
from typing import Any, AsyncGenerator, Dict, Union
from fastapi.responses import StreamingResponse
DATA_PREFIX = "data: "
TOKEN_DELAY = 0.03 # 30ms delay between tokens
PART_DELAY = 1.0 # 1s delay between parts
class VercelStreamResponse(StreamingResponse):
class SSEStreamResponse(StreamingResponse):
"""
Converts preprocessed events into Vercel-compatible streaming response format.
New SSE format compatible with Vercel/AI SDK 5 useChat
"""
TEXT_PREFIX = "0:"
DATA_PREFIX = "8:"
ERROR_PREFIX = "3:"
def __init__(self, parts: list[Union[str, Dict[str, Any]]], query: str = "", **kwargs):
stream = self._create_stream(query, parts)
super().__init__(
stream,
media_type="text/event-stream",
headers={"Connection": "keep-alive"},
**kwargs
)
def __init__(
self,
events: Iterable[Any],
*args: Any,
**kwargs: Any,
):
stream = self._stream_event(events=events)
super().__init__(stream, *args, **kwargs)
async def _create_stream(self, query: str, parts: list[Union[str, Dict[str, Any]]]) -> AsyncGenerator[str, None]:
"""Create SSE stream with new format"""
async def _stream_event(self, events: Iterable[Any]) -> AsyncGenerator[str, None]:
stream_started = False
for event in events:
if not stream_started:
yield self.convert_text("")
stream_started = True
# Simulate a small delay between events
await asyncio.sleep(0.1)
if isinstance(event, str):
yield self.convert_text(event)
elif isinstance(event, dict):
yield self.convert_data(event)
else:
raise ValueError(f"Unknown event type: {type(event)}")
async def write_text(content: str) -> AsyncGenerator[str, None]:
"""Write text content with token-by-token streaming"""
# Generate unique message id
message_id = str(uuid.uuid4())
@classmethod
def convert_text(cls, token: str) -> str:
"""Convert text event to Vercel format."""
# Escape newlines and double quotes to avoid breaking the stream
token = json.dumps(token)
return f"{cls.TEXT_PREFIX}{token}\n"
# Start text chunk
start_chunk = {"id": message_id, "type": "text-start"}
yield f"{DATA_PREFIX}{json.dumps(start_chunk)}\n\n"
@classmethod
def convert_data(cls, data: Union[dict, str]) -> str:
"""Convert data event to Vercel format."""
data_str = json.dumps(data) if isinstance(data, dict) else data
return f"{cls.DATA_PREFIX}[{data_str}]\n"
# Stream tokens
for token in content.split(' '):
if token: # Skip empty tokens
delta_chunk = {
"id": message_id,
"type": "text-delta",
"delta": token + " "
}
yield f"{DATA_PREFIX}{json.dumps(delta_chunk)}\n\n"
await asyncio.sleep(TOKEN_DELAY)
@classmethod
def convert_error(cls, error: str) -> str:
"""Convert error event to Vercel format."""
error_str = json.dumps(error)
return f"{cls.ERROR_PREFIX}{error_str}\n"
# End text chunk
end_chunk = {"id": message_id, "type": "text-end"}
yield f"{DATA_PREFIX}{json.dumps(end_chunk)}\n\n"
async def write_data(data: Dict[str, Any]) -> AsyncGenerator[str, None]:
"""Write data part"""
chunk = {
"type": f"data-{data['type']}", # Add data- prefix
"data": data.get("data", {})
}
# Only include id if it exists
if data.get("id"):
chunk["id"] = data["id"]
yield f"{DATA_PREFIX}{json.dumps(chunk)}\n\n"
await asyncio.sleep(PART_DELAY)
# Stream the query first
if query:
async for chunk in write_text(query):
yield chunk
# Stream all parts
for item in parts:
if isinstance(item, str):
async for chunk in write_text(item):
yield chunk
elif isinstance(item, dict):
async for chunk in write_data(item):
yield chunk
def get_text(message: Any) -> str:
return "\n\n".join(
part["text"]
for part in message["parts"]
if part.get("type") == "text" and "text" in part
)
-10
View File
@@ -1,10 +0,0 @@
module.exports = {
extends: ['@llamaindex/eslint-config/next.js'],
rules: {
'@typescript-eslint/no-unused-vars': 'off',
'no-await-in-loop': 'off',
'no-promise-executor-return': 'off',
'@typescript-eslint/no-loop-func': 'off',
'turbo/no-undeclared-env-vars': 'off',
},
}
-111
View File
@@ -244,116 +244,5 @@
}
body {
@apply bg-background text-foreground antialiased;
font-feature-settings: 'cv11', 'ss01';
font-variation-settings: 'opsz' 32;
}
html {
scroll-behavior: smooth;
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.3);
}
/* Selection styling */
::selection {
background: rgba(186, 186, 233, 0.3);
color: inherit;
}
::-moz-selection {
background: rgba(186, 186, 233, 0.3);
color: inherit;
}
/* Utility classes for animations */
.animate-fade-in {
animation: var(--animate-fade-in);
}
.animate-fade-in-up {
animation: var(--animate-fade-in-up);
}
.animate-scale-in {
animation: var(--animate-scale-in);
}
.animate-slide-in-right {
animation: var(--animate-slide-in-right);
}
.animate-pulse-glow {
animation: var(--animate-pulse-glow);
}
.animate-float {
animation: var(--animate-float);
}
/* Glass morphism utilities */
.glass {
background: var(--glass-gradient);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.glass-border {
border: 1px solid;
border-image: var(--glass-border) 1;
}
/* Shimmer effect */
.shimmer {
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.1) 50%,
rgba(255, 255, 255, 0) 100%
);
background-size: 1000px 100%;
animation: var(--animate-shimmer);
}
/* Text gradient utilities */
.text-gradient-purple {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.text-gradient-rainbow {
background: linear-gradient(
90deg,
#ff006e,
#8338ec,
#3a86ff,
#06ffa5,
#ffbe0b,
#fb5607
);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
background-size: 300% 100%;
animation: var(--animate-shimmer);
}
}
+2 -2
View File
@@ -8,8 +8,8 @@ import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'LlamaIndex Chat UI - Next.js Example',
description: 'A simple Next.js application using @llamaindex/chat-ui',
title: 'LlamaIndex Chat UI - FastAPI Example',
description: 'A simple interface using @llamaindex/chat-ui',
}
export default function RootLayout({
+25 -11
View File
@@ -8,14 +8,21 @@ import {
ChatSection,
useChatUI,
} from '@llamaindex/chat-ui'
import { Message, useChat } from 'ai/react'
import { CustomWeatherAnnotation } from '../components/custom-weather-annotation'
import { UIMessage, useChat } from '@ai-sdk/react'
import { WeatherPart } from '../components/custom-weather'
import { DefaultChatTransport } from 'ai'
import { WikiPart } from '../components/custom-wiki'
const initialMessages: Message[] = [
const initialMessages: UIMessage[] = [
{
id: '1',
content: 'Hello! How can I help you today?',
role: 'assistant',
parts: [
{
type: 'text',
text: 'Hello! How can I help you today?',
},
],
},
]
@@ -39,8 +46,10 @@ export default function Page(): JSX.Element {
function ChatExample() {
const handler = useChat({
api: 'http://localhost:8000/api/chat',
initialMessages,
transport: new DefaultChatTransport({
api: 'http://localhost:8000/api/chat',
}),
messages: initialMessages,
})
return (
@@ -69,7 +78,7 @@ function ChatExample() {
}
function CustomChatMessages() {
const { messages, isLoading, append } = useChatUI()
const { messages } = useChatUI()
return (
<>
@@ -85,10 +94,15 @@ function CustomChatMessages() {
{message.role === 'user' ? 'U' : 'AI'}
</div>
</ChatMessage.Avatar>
<ChatMessage.Content isLoading={isLoading} append={append}>
<ChatMessage.Content.Markdown />
<ChatMessage.Content.Source />
<CustomWeatherAnnotation />
<ChatMessage.Content>
<ChatMessage.Part.File />
<ChatMessage.Part.Event />
<ChatMessage.Part.Markdown />
<ChatMessage.Part.Artifact />
<ChatMessage.Part.Source />
<ChatMessage.Part.Suggestion />
<WikiPart />
<WeatherPart />
</ChatMessage.Content>
<ChatMessage.Actions />
</ChatMessage>
@@ -1,8 +1,8 @@
'use client'
import { useChatMessage, getAnnotationData } from '@llamaindex/chat-ui'
import { usePart } from '@llamaindex/chat-ui'
interface WeatherData {
type WeatherData = {
location: string
temperature: number
condition: string
@@ -10,14 +10,18 @@ interface WeatherData {
windSpeed: number
}
// A custom annotation component that is used to display weather information in a chat message
// The weather data is extracted from annotations in the message that has type 'weather'
export function WeatherAnnotation() {
const { message } = useChatMessage()
const weatherData = getAnnotationData<WeatherData>(message, 'weather')
const WeatherPartType = 'data-weather'
if (weatherData.length === 0) return null
return <WeatherCard data={weatherData[0]} />
type WeatherPart = {
type: typeof WeatherPartType
data: WeatherData
}
// A custom part component that is used to display weather information in a chat message
export function WeatherPart() {
const weatherData = usePart<WeatherPart>(WeatherPartType)?.data
if (!weatherData) return null
return <WeatherCard data={weatherData} />
}
function WeatherCard({ data }: { data: WeatherData }) {
@@ -29,13 +33,13 @@ function WeatherCard({ data }: { data: WeatherData }) {
stormy: '⛈️',
}
if (!data.location) return null
return (
<div className="rounded-lg border border-blue-200 bg-blue-50 p-4">
<div className="flex items-center gap-3">
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-blue-100">
<span className="text-2xl">{iconMap[data.condition] || '🌤️'}</span>
<span className="text-2xl">
{iconMap[data.condition.toLowerCase()] || '🌤️'}
</span>
</div>
<div className="flex-1">
<h3 className="font-semibold text-blue-900">{data.location}</h3>
@@ -0,0 +1,114 @@
'use client'
import { usePart } from '@llamaindex/chat-ui'
type WikiData = {
title: string
summary: string
url: string
category: string
lastUpdated: string
}
const WikiPartType = 'data-wiki'
type WikiPart = {
type: typeof WikiPartType
data: WikiData
}
export function WikiPart() {
const wikiData = usePart<WikiPart>(WikiPartType)?.data
if (!wikiData) return null
return <WikiCard data={wikiData} />
}
// A UI widget that displays wiki information, it can be used inline with markdown text
function WikiCard({ data }: { data: WikiData }) {
const iconMap: Record<string, string> = {
science: '🧪',
history: '📜',
technology: '💻',
biology: '🧬',
geography: '🌍',
literature: '📚',
art: '🎨',
music: '🎵',
}
const getCategoryColor = (category: string) => {
const colors: Record<string, string> = {
science: 'from-blue-50 to-blue-100 border-blue-200 text-blue-900',
history: 'from-amber-50 to-amber-100 border-amber-200 text-amber-900',
technology:
'from-purple-50 to-purple-100 border-purple-200 text-purple-900',
biology: 'from-green-50 to-green-100 border-green-200 text-green-900',
geography: 'from-teal-50 to-teal-100 border-teal-200 text-teal-900',
literature:
'from-indigo-50 to-indigo-100 border-indigo-200 text-indigo-900',
art: 'from-pink-50 to-pink-100 border-pink-200 text-pink-900',
music: 'from-violet-50 to-violet-100 border-violet-200 text-violet-900',
}
return (
colors[category.toLowerCase()] ||
'from-gray-50 to-gray-100 border-gray-200 text-gray-900'
)
}
const categoryColorClass = getCategoryColor(data.category)
return (
<div
className={`my-6 rounded-xl border bg-gradient-to-br p-6 shadow-sm transition-all duration-200 hover:shadow-md ${categoryColorClass}`}
>
<div className="flex items-start gap-4">
<div className="flex h-14 w-14 flex-shrink-0 items-center justify-center rounded-xl bg-white/50 shadow-sm backdrop-blur-sm">
<span className="text-3xl">
{iconMap[data.category.toLowerCase()] || '📖'}
</span>
</div>
<div className="min-w-0 flex-1">
<h3 className="mb-2 text-xl font-bold leading-tight">{data.title}</h3>
<p className="mb-4 text-base leading-relaxed opacity-80">
{data.summary}
</p>
</div>
</div>
<div className="mt-4 flex flex-wrap items-center gap-4 text-sm opacity-70">
<div className="flex items-center gap-2">
<span className="font-medium">📂</span>
<span className="capitalize">{data.category}</span>
</div>
<div className="flex items-center gap-2">
<span className="font-medium">📅</span>
<span>{data.lastUpdated}</span>
</div>
</div>
<div className="border-current/10 mt-4 border-t pt-4">
<a
href={data.url}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 rounded-lg bg-white/30 px-4 py-2 text-sm font-medium transition-all duration-200 hover:bg-white/50 hover:shadow-sm"
>
📖 Read full article
<svg
className="h-4 w-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
/>
</svg>
</a>
</div>
</div>
)
}
@@ -0,0 +1,22 @@
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
});
const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
{
rules: {
"@next/next/no-duplicate-head": "off",
"@typescript-eslint/no-explicit-any": "off",
}
}
];
export default eslintConfig;
+8 -5
View File
@@ -11,14 +11,17 @@
},
"dependencies": {
"@llamaindex/chat-ui": "latest",
"ai": "^4.3.16",
"next": "^15.3.2",
"@ai-sdk/react": "^2.0.4",
"@ai-sdk/rsc": "^1.0.4",
"ai": "^5.0.4",
"next": "15.3.8",
"react": "^19.1.0",
"react-dom": "^19.1.0"
},
"devDependencies": {
"@llamaindex/eslint-config": "workspace:*",
"@llamaindex/typescript-config": "workspace:*",
"eslint": "^9",
"eslint-config-next": "15.3.5",
"@eslint/eslintrc": "^3",
"@next/eslint-plugin-next": "^14.2.3",
"@tailwindcss/postcss": "^4.0.7",
"@types/node": "^20.11.24",
@@ -28,4 +31,4 @@
"tailwindcss": "^4.0.7",
"typescript": "^5.3.3"
}
}
}
@@ -13,8 +13,8 @@ services:
name: src
path: src/chat_workflow:workflow
python-dependencies:
- llama-index-llms-openai>=0.4.5
- llama-index-core>=0.12.45
- llama-index-llms-openai==0.4.5
- llama-index-core==0.12.45
agent_workflow:
name: Agent Workflow
source:
@@ -22,7 +22,7 @@ services:
name: src
path: src/agent_workflow:workflow
python-dependencies:
- llama-index-llms-openai>=0.4.5
- llama-index-llms-openai==0.4.5
cli_workflow:
name: CLI Workflow
source:

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

@@ -10,7 +10,7 @@ import {
useChatUI,
useChatWorkflow,
} from '@llamaindex/chat-ui'
import { WeatherAnnotation } from '@/components/custom/custom-weather'
import { WeatherPart } from '@/components/custom/custom-weather'
import { CLIHumanInput } from '@/components/custom/human-input'
import {
Select,
@@ -105,9 +105,9 @@ function CustomChatMessages({
<ChatMessage.Avatar />
<ChatMessage.Content isLoading={isLoading} append={append}>
<CLIHumanInput resumeWorkflow={resumeWorkflow} />
<ChatMessage.Content.Markdown />
<WeatherAnnotation />
<ChatMessage.Content.Source />
<ChatMessage.Part.Markdown />
<WeatherPart />
<ChatMessage.Part.Source />
</ChatMessage.Content>
<ChatMessage.Actions />
</ChatMessage>
@@ -1,8 +1,8 @@
'use client'
import { useChatMessage, getAnnotationData } from '@llamaindex/chat-ui'
import { usePart } from '@llamaindex/chat-ui'
interface WeatherData {
type WeatherData = {
location: string
temperature: number
condition: string
@@ -10,20 +10,36 @@ interface WeatherData {
windSpeed: number
}
export function CustomWeatherAnnotation() {
const { message } = useChatMessage()
const WeatherPartType = 'data-weather'
const weatherData = getAnnotationData<WeatherData>(message, 'weather')
type WeatherPart = {
type: typeof WeatherPartType
data: WeatherData
}
if (weatherData.length === 0) return null
// A custom part component that is used to display weather information in a chat message
export function WeatherPart() {
const weatherData = usePart<WeatherPart>(WeatherPartType)?.data
if (!weatherData) return null
return <WeatherCard data={weatherData} />
}
const data = weatherData[0]
function WeatherCard({ data }: { data: WeatherData }) {
const iconMap: Record<string, string> = {
sunny: '☀️',
cloudy: '☁️',
rainy: '🌧️',
snowy: '❄️',
stormy: '⛈️',
}
return (
<div className="my-4 rounded-lg border border-blue-200 bg-blue-50 p-4">
<div className="rounded-lg border border-blue-200 bg-blue-50 p-4">
<div className="flex items-center gap-3">
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-blue-100">
<WeatherIcon condition={data.condition} />
<span className="text-2xl">
{iconMap[data.condition.toLowerCase()] || '🌤️'}
</span>
</div>
<div className="flex-1">
<h3 className="font-semibold text-blue-900">{data.location}</h3>
@@ -46,17 +62,3 @@ export function CustomWeatherAnnotation() {
</div>
)
}
function WeatherIcon({ condition }: { condition: string }) {
const iconMap: Record<string, string> = {
sunny: '☀️',
cloudy: '☁️',
rainy: '🌧️',
snowy: '❄️',
stormy: '⛈️',
}
return (
<span className="text-2xl">{iconMap[condition.toLowerCase()] || '🌤️'}</span>
)
}
@@ -11,11 +11,12 @@
"dependencies": {
"@llamaindex/chat-ui": "latest",
"@radix-ui/react-select": "^2.1.1",
"ai": "^4.3.16",
"@ai-sdk/react": "^2.0.4",
"ai": "^5.0.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"lucide-react": "^0.453.0",
"next": "^15.3.2",
"next": "15.3.8",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"tailwind-merge": "^3.3.1"
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
These examples are using a generic chat UI using the [LlamaIndexServer](https://www.npmjs.com/package/@llamaindex/server) as a frontend.
@@ -1,8 +1,8 @@
# LlamaIndex Workflow Example
# LlamaDeploy + LlamaCloud + LlamaIndexServer Example
This is a [LlamaIndex](https://www.llamaindex.ai/) project that using [Workflows](https://docs.llamaindex.ai/en/stable/understanding/workflows/) deployed with [LlamaDeploy](https://github.com/run-llama/llama_deploy).
This is a [LlamaIndex](https://www.llamaindex.ai/) project that using [AgentWorkflows](https://docs.llamaindex.ai/en/stable/understanding/agent/) to deploy an agentic RAG with [LlamaDeploy](https://github.com/run-llama/llama_deploy) using an index hosted on [LlamaCloud](https://llamacloud.ai/).
LlamaDeploy is a system for deploying and managing LlamaIndex workflows, while LlamaIndexServer provides a pre-built TypeScript server with an integrated chat UI that can connect directly to LlamaDeploy deployments. This example shows how you can quickly set up a complete chat application by combining these two technologies/
LlamaDeploy is a system for deploying and managing LlamaIndex workflows, while LlamaIndexServer provides a pre-built TypeScript server with an integrated chat UI that can connect directly to LlamaDeploy deployments. This example shows how you can quickly set up a complete chat application by combining these two technologies.
## Prerequisites
@@ -20,11 +20,9 @@ Both the SDK and the CLI are part of the LlamaDeploy Python package. To install,
uv sync
```
If you don't have uv installed, you can follow the instructions [here](https://docs.astral.sh/uv/getting-started/installation/).
## Generate Index
Generate the embeddings of the documents in the `./data` directory:
Generate the embeddings of the documents in the [`./ui/data`](./ui/data) directory:
```shell
uv run generate
@@ -70,10 +68,12 @@ curl -X POST 'http://localhost:4501/deployments/chat/tasks/create' \
}'
```
Stream events:
Stream events (while the task is running):
```bash
curl 'http://localhost:4501/deployments/chat/tasks/0b411be6-005d-43f0-9b6b-6a0017f08002/events?session_id=dd36442c-45ca-4eaa-8d75-b4e6dad1a83e&raw_event=true' \
TASK_ID=0b411be6-005d-43f0-9b6b-6a0017f08002
SESSION_ID=dd36442c-45ca-4eaa-8d75-b4e6dad1a83e
curl 'http://localhost:4501/deployments/chat/tasks/$TASK_ID/events?session_id=$SESSION_ID&raw_event=true' \
-H 'Content-Type: application/json'
```
@@ -13,8 +13,8 @@ services:
name: src
path: src/workflow:workflow
python-dependencies:
- llama-index-llms-openai>=0.4.5
- llama-index-core>=0.12.45
- llama-index-llms-openai==0.4.5
- llama-index-core==0.12.45
ui:
name: My Nextjs App
@@ -0,0 +1,5 @@
# Check this documentation: https://docs.cloud.llamaindex.ai/llamacloud/getting_started/quick_start
# to create or obtain these configurations
LLAMA_CLOUD_API_KEY=
LLAMA_CLOUD_INDEX_NAME=chat-ui-example
LLAMA_CLOUD_PROJECT_NAME=Default
@@ -6,5 +6,8 @@ new LlamaIndexServer({
layoutDir: 'layout',
llamaDeploy: { deployment: 'chat', workflow: 'workflow' },
},
llamaCloud: {
outputDir: 'output/llamacloud',
},
port: 3000,
}).start()
File diff suppressed because it is too large Load Diff
@@ -1,8 +1,8 @@
# LlamaIndex Workflow Example
# LlamaDeploy + LlamaIndexServer Example
This is a [LlamaIndex](https://www.llamaindex.ai/) project that using [Workflows](https://docs.llamaindex.ai/en/stable/understanding/workflows/) deployed with [LlamaDeploy](https://github.com/run-llama/llama_deploy).
This is a [LlamaIndex](https://www.llamaindex.ai/) project that using [AgentWorkflows](https://docs.llamaindex.ai/en/stable/understanding/agent/) to deploy an agentic RAG with [LlamaDeploy](https://github.com/run-llama/llama_deploy).
LlamaDeploy is a system for deploying and managing LlamaIndex workflows, while LlamaIndexServer provides a pre-built TypeScript server with an integrated chat UI that can connect directly to LlamaDeploy deployments. This example shows how you can quickly set up a complete chat application by combining these two technologies/
LlamaDeploy is a system for deploying and managing LlamaIndex workflows, while LlamaIndexServer provides a pre-built TypeScript server with an integrated chat UI that can connect directly to LlamaDeploy deployments. This example shows how you can quickly set up a complete chat application by combining these two technologies.
## Prerequisites
@@ -20,11 +20,9 @@ Both the SDK and the CLI are part of the LlamaDeploy Python package. To install,
uv sync
```
If you don't have uv installed, you can follow the instructions [here](https://docs.astral.sh/uv/getting-started/installation/).
## Generate Index
Generate the embeddings of the documents in the `./data` directory:
Generate the embeddings of the documents in the [`./ui/data`](./ui/data) directory:
```shell
uv run generate
@@ -70,18 +68,20 @@ curl -X POST 'http://localhost:4501/deployments/chat/tasks/create' \
}'
```
Stream events:
Stream events (while the task is running):
```bash
curl 'http://localhost:4501/deployments/chat/tasks/0b411be6-005d-43f0-9b6b-6a0017f08002/events?session_id=dd36442c-45ca-4eaa-8d75-b4e6dad1a83e&raw_event=true' \
TASK_ID=0b411be6-005d-43f0-9b6b-6a0017f08002
SESSION_ID=dd36442c-45ca-4eaa-8d75-b4e6dad1a83e
curl 'http://localhost:4501/deployments/chat/tasks/$TASK_ID/events?session_id=$SESSION_ID&raw_event=true' \
-H 'Content-Type: application/json'
```
Note that the task_id and session_id are returned when creating a new task.
Note that the TASK_ID and SESSION_ID are returned when creating a new task.
## Use Case
We have prepared an [example workflow](./src/workflow.py) for the agentic RAG use case, where you can ask questions about the example documents in the [./data](./data) directory.
We have prepared an [example workflow](./src/workflow.py) for the agentic RAG use case, where you can ask questions about the example documents in the [./ui/data](./ui/data) directory.
To update the workflow, you can modify the code in [`src/workflow.py`](src/workflow.py).
## Customize the UI
@@ -93,7 +93,6 @@ The following are the available options:
- `starterQuestions`: Predefined questions for chat interface
- `componentsDir`: Directory for custom event components
- `layoutDir`: Directory for custom layout components
- `llamaCloudIndexSelector`: Enable LlamaCloud integration
- `llamaDeploy`: The LlamaDeploy configration (deployment name and workflow name that defined in the [llama_deploy.yml](llama_deploy.yml) file)
## Learn More
@@ -13,8 +13,8 @@ services:
name: src
path: src/workflow:workflow
python-dependencies:
- llama-index-llms-openai>=0.4.5
- llama-index-core>=0.12.45
- llama-index-llms-openai==0.4.5
- llama-index-core==0.12.45
ui:
name: My Nextjs App
File diff suppressed because it is too large Load Diff
@@ -1,17 +1,10 @@
# LlamaDeploy + LlamaIndexServer Example
This example demonstrates how to use **LlamaIndexServer** as a frontend chat interface for workflows deployed with [LlamaDeploy](https://github.com/run-llama/llama_deploy).
This example demonstrates how to use **LlamaIndexServer** as a frontend chat interface for a custom LlamaIndex workflow deployed with [LlamaDeploy](https://github.com/run-llama/llama_deploy).
LlamaDeploy is a system for deploying and managing LlamaIndex workflows, while LlamaIndexServer provides a pre-built TypeScript server with an integrated chat UI that can connect directly to LlamaDeploy deployments. This example shows how you can quickly set up a complete chat application by combining these two technologies without needing to build custom UI components.
## Key Features
- Multiple examples of workflows:
- [Custom Chat Workflow](src/chat_workflow.py)
- [Agent Workflow](src/agent_workflow.py)
- [Human in the Loop Workflow](src/cli_workflow.py)
Test the workflows by selecting one of them in the UI. The custom chat workflow is most sophisticated, as it supports sending annotations to the chat messages by sending specific events.
The chat interface is triggering a code generation workflow, for the source code check out the [workflow](src/workflow.py) file.
## Installation
@@ -49,7 +42,7 @@ with your deployment through a user-friendly interface.
## Learn More
- [useChatWorkflow Hook](../../docs/chat-ui/hooks.mdx#usechatworkflow)
- [useWorkflow Hook](../../docs/chat-ui/hooks.mdx#useworkflow)
- [useChatWorkflow Hook](../../../../../docs/chat-ui/hooks.mdx#usechatworkflow)
- [useWorkflow Hook](../../../../../docs/chat-ui/hooks.mdx#useworkflow)
- [LlamaDeploy GitHub Repository](https://github.com/run-llama/llama_deploy)
- [Chat-UI Documentation](../../docs/chat-ui/)
- [Chat-UI Documentation](../../../../../docs/chat-ui/)
@@ -13,8 +13,8 @@ services:
name: src
path: src/workflow:workflow
python-dependencies:
- llama-index-llms-openai>=0.4.5
- llama-index-core>=0.12.45
- llama-index-llms-openai==0.4.5
- llama-index-core==0.12.45
ui:
name: My Nextjs App

Some files were not shown because too many files have changed in this diff Show More