Initial commit: EGBE chatbot template
Stripped from vercel/chatbot (Apache 2.0): - Dropped @vercel/* packages and AI Gateway - Removed artifacts feature (code/text/sheet/image side panel) - Switched AI provider to @ai-sdk/openai-compatible -> EGBE LiteLLM - Replaced Vercel Blob upload with data URLs - Dropped Redis resumable streams and rate limiter (in-memory now) - Added Dockerfile (Next.js standalone) + entrypoint that runs migrations - Wired DATABASE_URL, EGBE_AI_API_URL/KEY, NEXT_PUBLIC_BASE_URL for app-deploy.sh
This commit is contained in:
commit
3e21c2334c
129 changed files with 21913 additions and 0 deletions
126
components/chat/messages.tsx
Normal file
126
components/chat/messages.tsx
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
import type { UseChatHelpers } from "@ai-sdk/react";
|
||||
import { ArrowDownIcon } from "lucide-react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useMessages } from "@/hooks/use-messages";
|
||||
import type { Vote } from "@/lib/db/schema";
|
||||
import type { ChatMessage } from "@/lib/types";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useDataStream } from "./data-stream-provider";
|
||||
import { Greeting } from "./greeting";
|
||||
import { PreviewMessage, ThinkingMessage } from "./message";
|
||||
|
||||
type MessagesProps = {
|
||||
addToolApprovalResponse: UseChatHelpers<ChatMessage>["addToolApprovalResponse"];
|
||||
chatId: string;
|
||||
status: UseChatHelpers<ChatMessage>["status"];
|
||||
votes: Vote[] | undefined;
|
||||
messages: ChatMessage[];
|
||||
setMessages: UseChatHelpers<ChatMessage>["setMessages"];
|
||||
regenerate: UseChatHelpers<ChatMessage>["regenerate"];
|
||||
isReadonly: boolean;
|
||||
isLoading?: boolean;
|
||||
selectedModelId: string;
|
||||
onEditMessage?: (message: ChatMessage) => void;
|
||||
};
|
||||
|
||||
function PureMessages({
|
||||
addToolApprovalResponse,
|
||||
chatId,
|
||||
status,
|
||||
votes,
|
||||
messages,
|
||||
setMessages,
|
||||
regenerate,
|
||||
isReadonly,
|
||||
isLoading,
|
||||
selectedModelId: _selectedModelId,
|
||||
onEditMessage,
|
||||
}: MessagesProps) {
|
||||
const {
|
||||
containerRef: messagesContainerRef,
|
||||
endRef: messagesEndRef,
|
||||
isAtBottom,
|
||||
scrollToBottom,
|
||||
hasSentMessage,
|
||||
reset,
|
||||
} = useMessages({
|
||||
status,
|
||||
});
|
||||
|
||||
useDataStream();
|
||||
|
||||
const prevChatIdRef = useRef(chatId);
|
||||
useEffect(() => {
|
||||
if (prevChatIdRef.current !== chatId) {
|
||||
prevChatIdRef.current = chatId;
|
||||
reset();
|
||||
}
|
||||
}, [chatId, reset]);
|
||||
|
||||
return (
|
||||
<div className="relative flex-1 bg-background">
|
||||
{messages.length === 0 && !isLoading && (
|
||||
<div className="pointer-events-none absolute inset-0 z-10 flex items-center justify-center">
|
||||
<Greeting />
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-0 touch-pan-y overflow-y-auto",
|
||||
messages.length > 0 ? "bg-background" : "bg-transparent"
|
||||
)}
|
||||
ref={messagesContainerRef}
|
||||
>
|
||||
<div className="mx-auto flex min-h-full min-w-0 max-w-4xl flex-col gap-5 px-2 py-6 md:gap-7 md:px-4">
|
||||
{messages.map((message, index) => (
|
||||
<PreviewMessage
|
||||
addToolApprovalResponse={addToolApprovalResponse}
|
||||
chatId={chatId}
|
||||
isLoading={
|
||||
status === "streaming" && messages.length - 1 === index
|
||||
}
|
||||
isReadonly={isReadonly}
|
||||
key={message.id}
|
||||
message={message}
|
||||
onEdit={onEditMessage}
|
||||
regenerate={regenerate}
|
||||
requiresScrollPadding={
|
||||
hasSentMessage && index === messages.length - 1
|
||||
}
|
||||
setMessages={setMessages}
|
||||
vote={
|
||||
votes
|
||||
? votes.find((vote) => vote.messageId === message.id)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
))}
|
||||
|
||||
{status === "submitted" && messages.at(-1)?.role !== "assistant" && (
|
||||
<ThinkingMessage />
|
||||
)}
|
||||
|
||||
<div
|
||||
className="min-h-[24px] min-w-[24px] shrink-0"
|
||||
ref={messagesEndRef}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
aria-label="Scroll to bottom"
|
||||
className={`absolute bottom-4 left-1/2 z-10 flex -translate-x-1/2 items-center rounded-full border border-border/50 bg-card/90 px-3.5 shadow-[var(--shadow-float)] backdrop-blur-lg transition-all duration-200 h-7 text-[10px] ${
|
||||
isAtBottom
|
||||
? "pointer-events-none scale-90 opacity-0"
|
||||
: "pointer-events-auto scale-100 opacity-100"
|
||||
}`}
|
||||
onClick={() => scrollToBottom("smooth")}
|
||||
type="button"
|
||||
>
|
||||
<ArrowDownIcon className="size-3 text-muted-foreground" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const Messages = PureMessages;
|
||||
Loading…
Add table
Add a link
Reference in a new issue