mirror of
https://github.com/run-llama/chat-ui.git
synced 2026-07-19 15:03:34 -04:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a514498830 | |||
| b27fae84a5 | |||
| 7d36f0f2f8 | |||
| 8fd4f4b3ec | |||
| b092d5ef4e | |||
| e09ac1cb06 | |||
| 10044f38b5 | |||
| 8cad367ae3 | |||
| 1ceb4ba041 | |||
| ee1a489dd5 | |||
| 5a9744c0c3 | |||
| 682e836bdb | |||
| 27d6c922fc | |||
| fa6e8510d6 | |||
| 75a8a5190e | |||
| f726f19a33 | |||
| e8a20643d1 | |||
| 6196621cd6 | |||
| 3acf2cf25c | |||
| 82bc236338 | |||
| 4aad967255 | |||
| bfdd3c012d | |||
| 685b79aee6 | |||
| c3e62265ca |
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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()
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -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')
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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',
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"core-components",
|
||||
"widgets",
|
||||
"hooks",
|
||||
"annotations",
|
||||
"parts",
|
||||
"artifacts",
|
||||
"customization",
|
||||
"examples"
|
||||
|
||||
@@ -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
@@ -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
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@llamaindex/chat-ui-docs",
|
||||
"version": "0.0.8",
|
||||
"version": "0.1.0",
|
||||
"files": [
|
||||
"chat-ui"
|
||||
],
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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',
|
||||
},
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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>
|
||||
|
||||
+16
-12
@@ -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;
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+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 |
+4
-4
@@ -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>
|
||||
+25
-23
@@ -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>
|
||||
)
|
||||
}
|
||||
+3
-2
@@ -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"
|
||||
+1860
-1492
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.
|
||||
+8
-8
@@ -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'
|
||||
```
|
||||
|
||||
+2
-2
@@ -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
|
||||
+3
@@ -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
+10
-11
@@ -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
|
||||
+2
-2
@@ -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
|
||||
+2649
File diff suppressed because it is too large
Load Diff
+5
-12
@@ -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/)
|
||||
+2
-2
@@ -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
Reference in New Issue
Block a user