prasunsrivastava
Added the app files with the fist version.
bd9a582
raw
history blame
11.4 kB
import React, { useState, useRef, useEffect } from "react";
interface Message {
role: "user" | "assistant";
content: string;
}
interface ChatInterfaceProps {
pipelineId: string | null;
onFileUpload: (file: File) => Promise<void>;
}
const TypingIndicator = () => (
<div className="flex justify-start">
<div className="bg-gray-800 text-gray-100 p-4 rounded-lg flex items-center space-x-2">
<div
className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"
style={{ animationDelay: "0ms" }}
></div>
<div
className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"
style={{ animationDelay: "150ms" }}
></div>
<div
className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"
style={{ animationDelay: "300ms" }}
></div>
</div>
</div>
);
const LoadingState = () => (
<div className="flex flex-col items-center justify-center h-full">
<div className="bg-gray-800 p-8 rounded-lg shadow-lg max-w-md w-full">
<div className="flex items-center justify-center mb-6">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div>
</div>
<div className="space-y-4">
<div className="h-4 bg-gray-700 rounded animate-pulse"></div>
<div className="h-4 bg-gray-700 rounded animate-pulse w-3/4"></div>
<div className="h-4 bg-gray-700 rounded animate-pulse w-1/2"></div>
</div>
<div className="mt-6 text-gray-400 text-center">
Processing your document...
</div>
</div>
</div>
);
const ErrorState = ({ onRetry }: { onRetry: () => void }) => (
<div className="flex flex-col items-center justify-center h-full">
<div className="bg-gray-800 p-8 rounded-lg shadow-lg max-w-md w-full">
<div className="flex items-center justify-center mb-6 text-red-500">
<svg
className="w-12 h-12"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
</div>
<div className="text-center">
<h3 className="text-lg font-medium text-white mb-2">
Document Upload Failed
</h3>
<p className="text-gray-400 mb-6">
There was an error processing your document. Please try again.
</p>
<button
onClick={onRetry}
className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors"
>
Try Again
</button>
</div>
</div>
</div>
);
const API_BASE_URL = process.env.REACT_APP_API_BASE_URL || "";
const ChatInterface: React.FC<ChatInterfaceProps> = ({
pipelineId,
onFileUpload,
}) => {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [isUploading, setIsUploading] = useState(false);
const [uploadError, setUploadError] = useState(false);
const messagesEndRef = useRef<null | HTMLDivElement>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (pipelineId) {
setIsUploading(false);
setMessages([
{
role: "assistant",
content:
"👋 Your document has been processed! You can now ask questions about its content.",
},
]);
}
}, [pipelineId]);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
};
useEffect(() => {
scrollToBottom();
}, [messages]);
const handleFileUpload = async (
event: React.ChangeEvent<HTMLInputElement>
) => {
const file = event.target.files?.[0];
if (file) {
try {
setIsUploading(true);
setUploadError(false);
await onFileUpload(file);
} catch (error) {
console.error("Error uploading file:", error);
setUploadError(true);
} finally {
setIsUploading(false);
}
}
};
const handleRetry = () => {
setUploadError(false);
// Reset file input
if (fileInputRef.current) {
fileInputRef.current.value = "";
}
};
const sendMessage = async (e: React.FormEvent) => {
e.preventDefault();
if (!input.trim() || !pipelineId) return;
const userMessage = input.trim();
setInput("");
setMessages((prev) => [...prev, { role: "user", content: userMessage }]);
setIsLoading(true);
try {
const response = await fetch(`${API_BASE_URL}/api/chat/${pipelineId}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ query: userMessage }),
});
const data = await response.json();
setMessages((prev) => [
...prev,
{ role: "assistant", content: data.response },
]);
} catch (error) {
console.error("Error sending message:", error);
setMessages((prev) => [
...prev,
{ role: "assistant", content: "Error: Failed to get response" },
]);
} finally {
setIsLoading(false);
}
};
return (
<div className="flex flex-col h-screen bg-gray-900">
{isUploading ? (
<LoadingState />
) : uploadError ? (
<ErrorState onRetry={handleRetry} />
) : !pipelineId ? (
<div className="flex flex-col items-center justify-center h-full">
<div className="bg-gray-800 p-8 rounded-lg shadow-lg max-w-md w-full">
<h2 className="text-2xl font-bold text-white mb-6 text-center">
Upload your document
</h2>
<label className="flex flex-col items-center justify-center w-full h-32 border-2 border-gray-600 border-dashed rounded-lg cursor-pointer hover:border-gray-500 hover:bg-gray-800/50 transition-all">
<div className="flex flex-col items-center justify-center pt-5 pb-6">
<svg
className="w-8 h-8 mb-4 text-gray-400"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 20 16"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"
/>
</svg>
<p className="mb-2 text-sm text-gray-400">
<span className="font-semibold">Click to upload</span> or drag
and drop
</p>
<p className="text-xs text-gray-400">PDF or TXT files</p>
</div>
<input
ref={fileInputRef}
type="file"
accept=".pdf,.txt"
onChange={handleFileUpload}
className="hidden"
/>
</label>
</div>
</div>
) : (
<div className="flex flex-col h-full max-w-5xl mx-auto w-full">
<div className="flex-1 overflow-y-auto p-4 space-y-4 mb-6">
{messages.map((message, index) => (
<div
key={index}
className={`flex ${
message.role === "user" ? "justify-end" : "justify-start"
}`}
>
<div
className={`max-w-[80%] p-4 rounded-lg shadow-lg ${
message.role === "user"
? "bg-blue-600 text-white"
: "bg-gray-800 text-gray-100"
}`}
>
{message.content}
</div>
</div>
))}
{isLoading && <TypingIndicator />}
{messages.length === 0 && !isLoading && (
<div className="flex justify-center items-center h-full">
<div className="text-gray-400 text-center">
<p>No messages yet</p>
<p className="text-sm mt-2">
Start by asking a question about your document
</p>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
<div className="px-4 pb-6">
<div className="bg-gray-800 rounded-lg p-2 shadow-lg border border-gray-700">
<form onSubmit={sendMessage} className="flex items-center gap-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Ask a question about your document..."
className="flex-1 p-3 bg-transparent text-white placeholder-gray-400 focus:outline-none"
disabled={isLoading}
/>
<button
type="submit"
disabled={isLoading || !input.trim()}
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-600 disabled:cursor-not-allowed transition-colors flex items-center gap-2 min-w-[100px] justify-center"
>
{isLoading ? (
<>
<svg
className="animate-spin h-4 w-4 text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<span>Sending...</span>
</>
) : (
<>
<span>Send</span>
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M14 5l7 7m0 0l-7 7m7-7H3"
/>
</svg>
</>
)}
</button>
</form>
</div>
</div>
</div>
)}
</div>
);
};
export default ChatInterface;