Team Ai
Apppublic

itsprasun/pythonic-rag-fastapi-react

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
ChatInterface.tsx329 linesDownload Raw Back to components
1import React, { useState, useRef, useEffect } from "react";2 3interface Message {4  role: "user" | "assistant";5  content: string;6}7 8interface ChatInterfaceProps {9  pipelineId: string | null;10  onFileUpload: (file: File) => Promise<void>;11}12 13const TypingIndicator = () => (14  <div className="flex justify-start">15    <div className="bg-gray-800 text-gray-100 p-4 rounded-lg flex items-center space-x-2">16      <div17        className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"18        style={{ animationDelay: "0ms" }}19      ></div>20      <div21        className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"22        style={{ animationDelay: "150ms" }}23      ></div>24      <div25        className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"26        style={{ animationDelay: "300ms" }}27      ></div>28    </div>29  </div>30);31 32const LoadingState = () => (33  <div className="flex flex-col items-center justify-center h-full">34    <div className="bg-gray-800 p-8 rounded-lg shadow-lg max-w-md w-full">35      <div className="flex items-center justify-center mb-6">36        <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div>37      </div>38      <div className="space-y-4">39        <div className="h-4 bg-gray-700 rounded animate-pulse"></div>40        <div className="h-4 bg-gray-700 rounded animate-pulse w-3/4"></div>41        <div className="h-4 bg-gray-700 rounded animate-pulse w-1/2"></div>42      </div>43      <div className="mt-6 text-gray-400 text-center">44        Processing your document...45      </div>46    </div>47  </div>48);49 50const ErrorState = ({ onRetry }: { onRetry: () => void }) => (51  <div className="flex flex-col items-center justify-center h-full">52    <div className="bg-gray-800 p-8 rounded-lg shadow-lg max-w-md w-full">53      <div className="flex items-center justify-center mb-6 text-red-500">54        <svg55          className="w-12 h-12"56          fill="none"57          stroke="currentColor"58          viewBox="0 0 24 24"59        >60          <path61            strokeLinecap="round"62            strokeLinejoin="round"63            strokeWidth={2}64            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"65          />66        </svg>67      </div>68      <div className="text-center">69        <h3 className="text-lg font-medium text-white mb-2">70          Document Upload Failed71        </h3>72        <p className="text-gray-400 mb-6">73          There was an error processing your document. Please try again.74        </p>75        <button76          onClick={onRetry}77          className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors"78        >79          Try Again80        </button>81      </div>82    </div>83  </div>84);85 86const API_BASE_URL = process.env.REACT_APP_API_BASE_URL || "";87 88const ChatInterface: React.FC<ChatInterfaceProps> = ({89  pipelineId,90  onFileUpload,91}) => {92  const [messages, setMessages] = useState<Message[]>([]);93  const [input, setInput] = useState("");94  const [isLoading, setIsLoading] = useState(false);95  const [isUploading, setIsUploading] = useState(false);96  const [uploadError, setUploadError] = useState(false);97  const messagesEndRef = useRef<null | HTMLDivElement>(null);98  const fileInputRef = useRef<HTMLInputElement>(null);99 100  useEffect(() => {101    if (pipelineId) {102      setIsUploading(false);103      setMessages([104        {105          role: "assistant",106          content:107            "๐Ÿ‘‹ Your document has been processed! You can now ask questions about its content.",108        },109      ]);110    }111  }, [pipelineId]);112 113  const scrollToBottom = () => {114    messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });115  };116 117  useEffect(() => {118    scrollToBottom();119  }, [messages]);120 121  const handleFileUpload = async (122    event: React.ChangeEvent<HTMLInputElement>123  ) => {124    const file = event.target.files?.[0];125    if (file) {126      try {127        setIsUploading(true);128        setUploadError(false);129        await onFileUpload(file);130      } catch (error) {131        console.error("Error uploading file:", error);132        setUploadError(true);133      } finally {134        setIsUploading(false);135      }136    }137  };138 139  const handleRetry = () => {140    setUploadError(false);141    // Reset file input142    if (fileInputRef.current) {143      fileInputRef.current.value = "";144    }145  };146 147  const sendMessage = async (e: React.FormEvent) => {148    e.preventDefault();149    if (!input.trim() || !pipelineId) return;150 151    const userMessage = input.trim();152    setInput("");153    setMessages((prev) => [...prev, { role: "user", content: userMessage }]);154    setIsLoading(true);155 156    try {157      const response = await fetch(`${API_BASE_URL}/api/chat/${pipelineId}`, {158        method: "POST",159        headers: {160          "Content-Type": "application/json",161        },162        body: JSON.stringify({ query: userMessage }),163      });164 165      const data = await response.json();166      setMessages((prev) => [167        ...prev,168        { role: "assistant", content: data.response },169      ]);170    } catch (error) {171      console.error("Error sending message:", error);172      setMessages((prev) => [173        ...prev,174        { role: "assistant", content: "Error: Failed to get response" },175      ]);176    } finally {177      setIsLoading(false);178    }179  };180 181  return (182    <div className="flex flex-col h-screen bg-gray-900">183      {isUploading ? (184        <LoadingState />185      ) : uploadError ? (186        <ErrorState onRetry={handleRetry} />187      ) : !pipelineId ? (188        <div className="flex flex-col items-center justify-center h-full">189          <div className="bg-gray-800 p-8 rounded-lg shadow-lg max-w-md w-full">190            <h2 className="text-2xl font-bold text-white mb-6 text-center">191              Upload your document192            </h2>193            <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">194              <div className="flex flex-col items-center justify-center pt-5 pb-6">195                <svg196                  className="w-8 h-8 mb-4 text-gray-400"197                  aria-hidden="true"198                  xmlns="http://www.w3.org/2000/svg"199                  fill="none"200                  viewBox="0 0 20 16"201                >202                  <path203                    stroke="currentColor"204                    strokeLinecap="round"205                    strokeLinejoin="round"206                    strokeWidth="2"207                    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"208                  />209                </svg>210                <p className="mb-2 text-sm text-gray-400">211                  <span className="font-semibold">Click to upload</span> or drag212                  and drop213                </p>214                <p className="text-xs text-gray-400">PDF or TXT files</p>215              </div>216              <input217                ref={fileInputRef}218                type="file"219                accept=".pdf,.txt"220                onChange={handleFileUpload}221                className="hidden"222              />223            </label>224          </div>225        </div>226      ) : (227        <div className="flex flex-col h-full max-w-5xl mx-auto w-full">228          <div className="flex-1 overflow-y-auto p-4 space-y-4 mb-6">229            {messages.map((message, index) => (230              <div231                key={index}232                className={`flex ${233                  message.role === "user" ? "justify-end" : "justify-start"234                }`}235              >236                <div237                  className={`max-w-[80%] p-4 rounded-lg shadow-lg ${238                    message.role === "user"239                      ? "bg-blue-600 text-white"240                      : "bg-gray-800 text-gray-100"241                  }`}242                >243                  {message.content}244                </div>245              </div>246            ))}247            {isLoading && <TypingIndicator />}248            {messages.length === 0 && !isLoading && (249              <div className="flex justify-center items-center h-full">250                <div className="text-gray-400 text-center">251                  <p>No messages yet</p>252                  <p className="text-sm mt-2">253                    Start by asking a question about your document254                  </p>255                </div>256              </div>257            )}258            <div ref={messagesEndRef} />259          </div>260          <div className="px-4 pb-6">261            <div className="bg-gray-800 rounded-lg p-2 shadow-lg border border-gray-700">262              <form onSubmit={sendMessage} className="flex items-center gap-2">263                <input264                  type="text"265                  value={input}266                  onChange={(e) => setInput(e.target.value)}267                  placeholder="Ask a question about your document..."268                  className="flex-1 p-3 bg-transparent text-white placeholder-gray-400 focus:outline-none"269                  disabled={isLoading}270                />271                <button272                  type="submit"273                  disabled={isLoading || !input.trim()}274                  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"275                >276                  {isLoading ? (277                    <>278                      <svg279                        className="animate-spin h-4 w-4 text-white"280                        xmlns="http://www.w3.org/2000/svg"281                        fill="none"282                        viewBox="0 0 24 24"283                      >284                        <circle285                          className="opacity-25"286                          cx="12"287                          cy="12"288                          r="10"289                          stroke="currentColor"290                          strokeWidth="4"291                        ></circle>292                        <path293                          className="opacity-75"294                          fill="currentColor"295                          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"296                        ></path>297                      </svg>298                      <span>Sending...</span>299                    </>300                  ) : (301                    <>302                      <span>Send</span>303                      <svg304                        className="w-4 h-4"305                        fill="none"306                        stroke="currentColor"307                        viewBox="0 0 24 24"308                      >309                        <path310                          strokeLinecap="round"311                          strokeLinejoin="round"312                          strokeWidth={2}313                          d="M14 5l7 7m0 0l-7 7m7-7H3"314                        />315                      </svg>316                    </>317                  )}318                </button>319              </form>320            </div>321          </div>322        </div>323      )}324    </div>325  );326};327 328export default ChatInterface;329