Skip to Content

ai-chat

ui

Complete AI conversation interface with message history, streaming, and suggested prompts.

Source Code

View the full component source code below.

"use client"

import * as React from "react"
import { Bot, Loader2, User } from "lucide-react"

import { cn } from "@/lib/utils"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import { ScrollArea } from "@/components/ui/scroll-area"
import { PromptInput } from "@/components/ui/prompt-input"

interface AiMessage {
  role: "user" | "assistant"
  content: string
}

interface AiChatProps extends React.ComponentProps<"div"> {
  messages: AiMessage[]
  onSend?: (message: string) => void
  isStreaming?: boolean
  suggestedPrompts?: string[]
}

function AiMessageBubble({ message }: { message: AiMessage }) {
  const isUser = message.role === "user"

  return (
    <div
      className={cn(
        "flex items-start gap-3",
        isUser ? "flex-row-reverse" : "flex-row"
      )}
    >
      <Avatar size="sm" className="mt-0.5 shrink-0">
        <AvatarFallback
          className={cn(
            isUser
              ? "bg-primary text-primary-foreground"
              : "bg-tanzanite/20 text-tanzanite"
          )}
        >
          {isUser ? <User className="size-3.5" /> : <Bot className="size-3.5" />}
        </AvatarFallback>
      </Avatar>

      <div
        className={cn(
          "max-w-[80%] rounded-2xl px-4 py-2.5 text-sm",
          isUser
            ? "bg-primary text-primary-foreground rounded-br-sm"
            : "bg-muted text-foreground rounded-bl-sm"
        )}
      >
        <p className="whitespace-pre-wrap break-words">{message.content}</p>
      </div>
    </div>
  )
}

function StreamingIndicator() {
  return (
    <div className="flex items-start gap-3">
      <Avatar size="sm" className="mt-0.5 shrink-0">
        <AvatarFallback className="bg-tanzanite/20 text-tanzanite">
          <Bot className="size-3.5" />
        </AvatarFallback>
      </Avatar>
      <div className="flex items-center gap-2 rounded-2xl rounded-bl-sm bg-muted px-4 py-3 text-sm text-muted-foreground">
        <Loader2 className="size-4 animate-spin" />
        <span>shamwari is thinking...</span>
      </div>
    </div>
  )
}

function AiChat({
  className,
  messages,
  onSend,
  isStreaming = false,
  suggestedPrompts,
  ...props
}: AiChatProps) {
  const scrollRef = React.useRef<HTMLDivElement>(null)

  React.useEffect(() => {
    if (scrollRef.current) {
      scrollRef.current.scrollTop = scrollRef.current.scrollHeight
    }
  }, [messages, isStreaming])

  const showSuggestions =
    messages.length === 0 && suggestedPrompts && suggestedPrompts.length > 0

  return (
    <div
      data-slot="ai-chat"
      className={cn("flex h-full flex-col", className)}
      {...props}
    >
      <ScrollArea className="flex-1">
        <div ref={scrollRef} className="flex flex-col gap-4 p-4">
          {showSuggestions && (
            <div className="flex flex-1 flex-col items-center justify-center gap-4 py-12">
              <div className="flex items-center gap-2 text-muted-foreground">
                <Bot className="size-5 text-tanzanite" />
                <span className="text-sm font-medium">
                  How can shamwari help you?
                </span>
              </div>
              <div className="flex flex-wrap justify-center gap-2">
                {suggestedPrompts.map((prompt) => (
                  <button
                    key={prompt}
                    type="button"
                    onClick={() => onSend?.(prompt)}
                    className="rounded-xl border border-border bg-card px-3 py-2 text-sm text-foreground transition-colors hover:bg-muted focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none"
                  >
                    {prompt}
                  </button>
                ))}
              </div>
            </div>
          )}

          {messages.map((message, index) => (
            <AiMessageBubble key={index} message={message} />
          ))}

          {isStreaming && <StreamingIndicator />}
        </div>
      </ScrollArea>

      <div className="border-t border-border p-4">
        <PromptInput
          onSend={onSend}
          onStop={() => {}}
          isStreaming={isStreaming}
          placeholder="Ask shamwari anything..."
        />
      </div>
    </div>
  )
}

export { AiChat, AiMessageBubble, StreamingIndicator }
export type { AiChatProps, AiMessage }

Installation

npx shadcn@latest add https://registry.mukoko.com/api/v1/ui/ai-chat

API

Fetch this component's metadata and source code from the registry API.

GET/api/v1/ui/ai-chat

Source

components/ui/ai-chat.tsx