Add Chat input

This commit is contained in:
abhi1992002
2026-02-03 10:52:22 +05:30
parent 6e0fbdea3c
commit 31ec5f5c17
3 changed files with 135 additions and 134 deletions

View File

@@ -26,81 +26,61 @@ export const ChatMessagesContainer = ({
messages,
status,
error,
handleSubmit,
input,
setInput,
}: ChatMessagesContainerProps) => {
return (
<div className="flex h-full flex-1 flex-col">
<Conversation className="flex-1">
<ConversationContent>
{messages.length === 0 ? (
<ConversationEmptyState
icon={<MessageSquareIcon className="size-12" />}
title="Start a conversation"
description="Type a message below to begin chatting"
/>
) : (
messages.map((message) => (
<Message from={message.role} key={message.id}>
<MessageContent>
{message.parts.map((part, i) => {
switch (part.type) {
case "text":
return (
<MessageResponse key={`${message.id}-${i}`}>
{part.text}
</MessageResponse>
);
case "tool-find_block":
return (
<FindBlocksTool
key={`${message.id}-${i}`}
part={part as ToolUIPart}
/>
);
default:
return null;
}
})}
</MessageContent>
</Message>
))
)}
{status === "submitted" && (
<Message from="assistant">
<MessageContent>
<p className="text-zinc-500">Thinking...</p>
<Conversation className="flex-1">
<ConversationContent>
{messages.length === 0 ? (
<ConversationEmptyState
icon={<MessageSquareIcon className="size-12" />}
title="Start a conversation"
description="Type a message below to begin chatting"
/>
) : (
messages.map((message) => (
<Message from={message.role} key={message.id}>
<MessageContent className={
"border rounded-xl px-3 py-2 " +
"group-[.is-user]:bg-purple-100 group-[.is-user]:rounded-2xl group-[.is-user]:border-purple-200 group-[.is-user]:text-slate-900 " +
"group-[.is-assistant]:bg-slate-50/20 group-[.is-assistant]:border-none group-[.is-assistant]:text-slate-900"
}>
{message.parts.map((part, i) => {
switch (part.type) {
case "text":
return (
<MessageResponse key={`${message.id}-${i}`}>
{part.text}
</MessageResponse>
);
case "tool-find_block":
return (
<FindBlocksTool
key={`${message.id}-${i}`}
part={part as ToolUIPart}
/>
);
default:
return null;
}
})}
</MessageContent>
</Message>
)}
{error && (
<div className="rounded-lg bg-red-50 p-3 text-red-600">
Error: {error.message}
</div>
)}
</ConversationContent>
<ConversationScrollButton />
</Conversation>
<form onSubmit={handleSubmit} className="border-t p-4">
<div className="mx-auto flex max-w-2xl gap-2">
<input
value={input}
onChange={(e) => setInput(e.target.value)}
disabled={status !== "ready"}
placeholder="Say something..."
className="flex-1 rounded-md border border-zinc-300 px-4 py-2 focus:border-zinc-500 focus:outline-none"
/>
<button
type="submit"
disabled={status !== "ready" || !input.trim()}
className="rounded-md bg-zinc-900 px-4 py-2 text-white transition-colors hover:bg-zinc-800 disabled:opacity-50"
>
Send
</button>
</div>
</form>
</div>
))
)}
{status === "submitted" && (
<Message from="assistant">
<MessageContent>
<p className="text-zinc-500">Thinking...</p>
</MessageContent>
</Message>
)}
{error && (
<div className="rounded-lg bg-red-50 p-3 text-red-600">
Error: {error.message}
</div>
)}
</ConversationContent>
<ConversationScrollButton />
</Conversation>
);
};

View File

@@ -8,6 +8,7 @@ import { ChatSidebar } from "./components/ChatSidebar/ChatSidebar";
import { EmptySession } from "./components/EmptySession/EmptySession";
import { ChatMessagesContainer } from "./components/ChatMessagesContainer/ChatMessagesContainer";
import { postV2CreateSession } from "@/app/api/__generated__/endpoints/chat/chat";
import { ChatInput } from "@/components/contextual/Chat/components/ChatInput/ChatInput";
export default function Page() {
const [sessionId, setSessionId] = useQueryState("sessionId", parseAsString);
@@ -62,22 +63,43 @@ export default function Page() {
setInput("");
}
function onSend(message: string) {
sendMessage({ text: message });
}
return (
<div className="flex h-full">
<ChatSidebar isCreating={isCreating} setIsCreating={setIsCreating} />
{sessionId ? (
<ChatMessagesContainer
messages={messages}
status={status}
error={error}
handleSubmit={handleMessageSubmit}
input={input}
setInput={setInput}
<div className="mx-auto h-[calc(100vh-60px)] max-w-3xl pb-6">
<div className="flex h-full flex-col">
{sessionId ? (
<ChatMessagesContainer
messages={messages}
status={status}
error={error}
handleSubmit={handleMessageSubmit}
input={input}
setInput={setInput}
/>
) : (
<EmptySession
isCreating={isCreating}
onCreateSession={createSession}
/>
)}
<div className="relative px-3 pt-2">
<div className="pointer-events-none absolute top-[-18px] z-10 h-6 w-full bg-gradient-to-b from-transparent to-[#f8f8f9]" />
<ChatInput
onSend={onSend}
disabled={status === "streaming" || !sessionId}
isStreaming={status === "streaming"}
onStop={() => {}}
placeholder="You can search or just ask"
/>
) : (
<EmptySession isCreating={isCreating} onCreateSession={createSession} />
)}
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,57 +1,56 @@
import { ToolUIPart } from "ai";
import { FindBlockInput, FindBlockOutput, FindBlockToolPart } from "./FindBlocks";
import { CheckCircleIcon, CircleNotchIcon, XCircleIcon } from "@phosphor-icons/react";
import {
FindBlockInput,
FindBlockOutput,
FindBlockToolPart,
} from "./FindBlocks";
import {
CheckCircleIcon,
CircleNotchIcon,
XCircleIcon,
} from "@phosphor-icons/react";
export const getAnimationText = (part: FindBlockToolPart): string => {
switch (part.state) {
case "input-streaming":
return "Searching blocks for you";
case "input-available": {
const query = (part.input as FindBlockInput).query;
return `Finding "${query}" blocks`;
}
case "output-available": {
const parsed = JSON.parse(part.output as string) as FindBlockOutput;
if (parsed) {
return `Found ${parsed.count} "${(part.input as FindBlockInput).query}" blocks`;
}
return "Found blocks";
}
case "output-error":
return "Error finding blocks";
default:
return "Processing";
switch (part.state) {
case "input-streaming":
return "Searching blocks for you";
case "input-available": {
const query = (part.input as FindBlockInput).query;
return `Finding "${query}" blocks`;
}
}
export const StateIcon = ({ state }: { state: ToolUIPart["state"] }) => {
switch (state) {
case "input-streaming":
case "input-available":
return (
<CircleNotchIcon
className="h-4 w-4 animate-spin text-muted-foreground"
weight="bold"
/>
);
case "output-available":
return (
<CheckCircleIcon
className="h-4 w-4 text-green-500"
/>
);
case "output-error":
return (
<XCircleIcon
className="h-4 w-4 text-red-500"
/>
);
default:
return null;
case "output-available": {
const parsed = JSON.parse(part.output as string) as FindBlockOutput;
if (parsed) {
return `Found ${parsed.count} "${(part.input as FindBlockInput).query}" blocks`;
}
return "Found blocks";
}
case "output-error":
return "Error finding blocks";
default:
return "Processing";
}
};
export const StateIcon = ({ state }: { state: ToolUIPart["state"] }) => {
switch (state) {
case "input-streaming":
case "input-available":
return (
<CircleNotchIcon
className="h-4 w-4 animate-spin text-muted-foreground"
weight="bold"
/>
);
case "output-available":
return <CheckCircleIcon className="h-4 w-4 text-green-500" />;
case "output-error":
return <XCircleIcon className="h-4 w-4 text-red-500" />;
default:
return null;
}
};