diff --git a/customer-support-agent/app/page.tsx b/customer-support-agent/app/page.tsx
index 77baa8e7..b6e119ec 100644
--- a/customer-support-agent/app/page.tsx
+++ b/customer-support-agent/app/page.tsx
@@ -1,8 +1,11 @@
-import React from "react";
+import React, { useState, useRef } from "react";
import dynamic from "next/dynamic";
import TopNavBar from "@/components/TopNavBar";
import ChatArea from "@/components/ChatArea";
import config from "@/config";
+import { readFileAsText, readFileAsBase64, readFileAsPDFText } from "@/utils/fileHandling";
+import { toast } from "@/hooks/use-toast";
+import FilePreview from "@/components/FilePreview";
const LeftSidebar = dynamic(() => import("@/components/LeftSidebar"), {
ssr: false,
@@ -12,12 +15,109 @@ const RightSidebar = dynamic(() => import("@/components/RightSidebar"), {
});
export default function Home() {
+ const [currentUpload, setCurrentUpload] = useState(null);
+ const fileInputRef = useRef(null);
+ const [isUploading, setIsUploading] = useState(false);
+
+ const handleFileSelect = async (e) => {
+ const file = e.target.files?.[0];
+ if (!file) return;
+
+ setIsUploading(true);
+
+ let loadingToastRef;
+
+ if (file.type === "application/pdf") {
+ loadingToastRef = toast({
+ title: "Processing PDF",
+ description: "Extracting text content...",
+ duration: Infinity,
+ });
+ }
+
+ try {
+ const isImage = file.type.startsWith("image/");
+ const isPDF = file.type === "application/pdf";
+ let base64Data = "";
+ let isText = false;
+
+ if (isImage) {
+ base64Data = await readFileAsBase64(file);
+ isText = false;
+ } else if (isPDF) {
+ try {
+ const pdfText = await readFileAsPDFText(file);
+ base64Data = btoa(encodeURIComponent(pdfText));
+ isText = true;
+ } catch (error) {
+ console.error("Failed to parse PDF:", error);
+ toast({
+ title: "PDF parsing failed",
+ description: "Unable to extract text from the PDF",
+ variant: "destructive",
+ });
+ return;
+ }
+ } else {
+ try {
+ const textContent = await readFileAsText(file);
+ base64Data = btoa(encodeURIComponent(textContent));
+ isText = true;
+ } catch (error) {
+ console.error("Failed to read as text:", error);
+ toast({
+ title: "Invalid file type",
+ description: "File must be readable as text, PDF, or be an image",
+ variant: "destructive",
+ });
+ return;
+ }
+ }
+
+ setCurrentUpload({
+ base64: base64Data,
+ fileName: file.name,
+ mediaType: isText ? "text/plain" : file.type,
+ isText,
+ });
+
+ toast({
+ title: "File uploaded",
+ description: `${file.name} ready to analyze`,
+ });
+ } catch (error) {
+ console.error("Error processing file:", error);
+ toast({
+ title: "Upload failed",
+ description: "Failed to process the file",
+ variant: "destructive",
+ });
+ } finally {
+ setIsUploading(false);
+ if (loadingToastRef) {
+ loadingToastRef.dismiss();
+ if (file.type === "application/pdf") {
+ toast({
+ title: "PDF Processed",
+ description: "Text extracted successfully",
+ });
+ }
+ }
+ }
+ };
+
return (
{config.includeLeftSidebar && }
-
+
{config.includeRightSidebar && }
diff --git a/customer-support-agent/components/ChatArea.tsx b/customer-support-agent/components/ChatArea.tsx
index 12b47538..bd9f7a11 100644
--- a/customer-support-agent/components/ChatArea.tsx
+++ b/customer-support-agent/components/ChatArea.tsx
@@ -14,6 +14,7 @@ import {
BookOpenText,
ChevronDown,
Send,
+ Paperclip,
} from "lucide-react";
import "highlight.js/styles/atom-one-dark.css";
import { Card, CardContent, CardFooter } from "@/components/ui/card";
@@ -25,6 +26,8 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
+import FilePreview from "@/components/FilePreview";
+import { toast } from "@/hooks/use-toast";
const TypedText = ({ text = "", delay = 5 }) => {
const [displayedText, setDisplayedText] = useState("");
@@ -200,6 +203,12 @@ interface Message {
id: string;
role: string;
content: string;
+ file?: {
+ base64: string;
+ fileName: string;
+ mediaType: string;
+ isText?: boolean;
+ };
}
// Define the props interface for ConversationHeader
@@ -297,7 +306,19 @@ const ConversationHeader: React.FC = ({
);
-function ChatArea() {
+function ChatArea({
+ currentUpload,
+ setCurrentUpload,
+ fileInputRef,
+ isUploading,
+ handleFileSelect,
+}: {
+ currentUpload: any;
+ setCurrentUpload: any;
+ fileInputRef: any;
+ isUploading: boolean;
+ handleFileSelect: any;
+}) {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState("");
const [isLoading, setIsLoading] = useState(false);
@@ -415,6 +436,7 @@ function ChatArea() {
id: crypto.randomUUID(),
role: "user",
content: typeof event === "string" ? event : input,
+ file: currentUpload || undefined,
};
const placeholderMessage = {
@@ -656,6 +678,11 @@ function ChatArea() {
content={message.content}
role={message.role}
/>
+ {message.file && (
+
+
+
+ )}
{message.role === "assistant" && (
@@ -690,6 +717,30 @@ function ChatArea() {
rows={1}
/>
+
+
+
+ {currentUpload && (
+
setCurrentUpload(null)}
+ />
+ )}
+
{
const [shouldShowSources, setShouldShowSources] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false);
const [selectedSource, setSelectedSource] = useState(null);
+ const [uploadedFiles, setUploadedFiles] = useState([]);
useEffect(() => {
const updateRAGSources = (
@@ -99,6 +101,11 @@ const RightSidebar: React.FC = () => {
setShouldShowSources(shouldShow);
};
+ const handleFileUpload = (event: CustomEvent) => {
+ const file = event.detail;
+ setUploadedFiles((prevFiles) => [...prevFiles, file]);
+ };
+
window.addEventListener(
"updateRagSources" as any,
updateRAGSources as EventListener,
@@ -107,6 +114,10 @@ const RightSidebar: React.FC = () => {
"updateSidebar" as any,
updateDebug as EventListener,
);
+ window.addEventListener(
+ "fileUpload" as any,
+ handleFileUpload as EventListener,
+ );
return () => {
window.removeEventListener(
@@ -117,6 +128,10 @@ const RightSidebar: React.FC = () => {
"updateSidebar" as any,
updateDebug as EventListener,
);
+ window.removeEventListener(
+ "fileUpload" as any,
+ handleFileUpload as EventListener,
+ );
};
}, []);
@@ -144,7 +159,7 @@ const RightSidebar: React.FC = () => {
- {ragHistory.length === 0 && (
+ {ragHistory.length === 0 && uploadedFiles.length === 0 && (
The assistant will display sources here once finding them
@@ -196,6 +211,18 @@ const RightSidebar: React.FC = () => {
))}
))}
+ {uploadedFiles.length > 0 && (
+
+
+ Uploaded Files
+
+ {uploadedFiles.map((file, index) => (
+
+
+
+ ))}
+
+ )}