From a42556e222bcd3bc6e01807dc200cad910d18719 Mon Sep 17 00:00:00 2001 From: Anirudh Ramchandran Date: Wed, 29 Jan 2025 15:49:36 -0500 Subject: [PATCH 01/29] fix ts bug --- app/frontend/src/components/NavBar.tsx | 39 +++++++++++++++----------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/app/frontend/src/components/NavBar.tsx b/app/frontend/src/components/NavBar.tsx index 7a8b9eff..5e38ad3e 100644 --- a/app/frontend/src/components/NavBar.tsx +++ b/app/frontend/src/components/NavBar.tsx @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: © 2024 Tenstorrent AI ULC "use client"; -import { useMemo, useRef, useEffect } from "react"; +import React, { useMemo, useRef, useEffect } from "react"; import { NavLink, useLocation, useNavigate } from "react-router-dom"; import { motion } from "framer-motion"; import { @@ -45,19 +45,21 @@ interface AnimatedIconProps { className?: string; } -const AnimatedIcon: React.FC = ({ - icon: Icon, - ...props -}) => ( - - - +const AnimatedIcon = React.forwardRef( + ({ icon: Icon, ...props }, ref) => ( + + + + ) ); +AnimatedIcon.displayName = "AnimatedIcon"; + export default function NavBar() { const location = useLocation(); const navigate = useNavigate(); @@ -298,7 +300,8 @@ export default function NavBar() { {isChatUI ? ( - @@ -308,7 +311,8 @@ export default function NavBar() { ) : ( <> - Logs @@ -333,7 +337,8 @@ export default function NavBar() { models.length > 0 ? "" : "opacity-50 cursor-not-allowed" }`} > - {!isChatUI && Chat UI} @@ -363,7 +368,8 @@ export default function NavBar() { models.length > 0 ? "" : "opacity-50 cursor-not-allowed" }`} > - {!isChatUI && Object Detection} @@ -478,4 +484,3 @@ export default function NavBar() { ); } - From 8eef00fcd727eed7f7415585858b81534d5415b7 Mon Sep 17 00:00:00 2001 From: Anirudh Ramchandran Date: Thu, 30 Jan 2025 08:50:45 -0500 Subject: [PATCH 02/29] add console log --- app/frontend/src/components/chatui/runInference.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/frontend/src/components/chatui/runInference.ts b/app/frontend/src/components/chatui/runInference.ts index 3b61f4c7..e0073e5c 100644 --- a/app/frontend/src/components/chatui/runInference.ts +++ b/app/frontend/src/components/chatui/runInference.ts @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: © 2024 Tenstorrent AI ULC -import { +import type { InferenceRequest, RagDataSource, ChatMessage, @@ -23,6 +23,8 @@ export const runInference = async ( try { setIsStreaming(true); + console.log("Uploaded files:", request.files); + let ragContext: { documents: string[] } | null = null; if (ragDatasource) { @@ -34,7 +36,7 @@ export const runInference = async ( console.log("RAG context being passed to generatePrompt:", ragContext); const messages = generatePrompt( chatHistory.map((msg) => ({ sender: msg.sender, text: msg.text })), - ragContext, + ragContext ); console.log("Generated messages:", messages); @@ -84,7 +86,7 @@ export const runInference = async ( console.log( "Sending request to model:", - JSON.stringify(requestBody, null, 2), + JSON.stringify(requestBody, null, 2) ); const response = await fetch(API_URL, { @@ -151,7 +153,7 @@ export const runInference = async ( user_tpot: jsonData.tpot, tokens_decoded: jsonData.tokens_decoded, tokens_prefilled: jsonData.tokens_prefilled, - context_length: jsonData.context_length + context_length: jsonData.context_length, }; console.log("Final Inference Stats received:", inferenceStats); continue; // Skip processing this chunk as part of the generated text @@ -186,7 +188,7 @@ export const runInference = async ( if (inferenceStats) { console.log( "Updating chat history with inference stats:", - inferenceStats, + inferenceStats ); setChatHistory((prevHistory) => { const updatedHistory = [...prevHistory]; From 9f108640b28398394de60d84655a32035202dee9 Mon Sep 17 00:00:00 2001 From: Anirudh Ramchandran Date: Thu, 30 Jan 2025 09:01:16 -0500 Subject: [PATCH 03/29] adds support to input area to allow for file upload --- .../src/components/chatui/InputArea.tsx | 170 ++++++++++++++++-- .../src/components/chatui/fileUtils.tsx | 81 +++++++++ 2 files changed, 236 insertions(+), 15 deletions(-) create mode 100644 app/frontend/src/components/chatui/fileUtils.tsx diff --git a/app/frontend/src/components/chatui/InputArea.tsx b/app/frontend/src/components/chatui/InputArea.tsx index 00e9df32..b55cd9ad 100644 --- a/app/frontend/src/components/chatui/InputArea.tsx +++ b/app/frontend/src/components/chatui/InputArea.tsx @@ -1,17 +1,32 @@ // SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: © 2024 Tenstorrent AI ULC -import React, { useEffect, useRef } from "react"; +// SPDX-FileCopyrightText: © 2025 Tenstorrent AI ULC +import type React from "react"; +import { useEffect, useRef, useState } from "react"; import { Button } from "../ui/button"; -import { Paperclip, Send } from "lucide-react"; +import { Paperclip, Send, X, File } from "lucide-react"; import { VoiceInput } from "./VoiceInput"; +import { FileUpload } from "../ui/file-upload"; +import { encodeFileToBase64, isImageFile, validateFile } from "./fileUtils"; +import { cn } from "../../lib/utils"; + +interface FileData { + type: "text" | "image_url"; + text?: string; + image_url?: { + url: string; + }; + name: string; +} interface InputAreaProps { textInput: string; setTextInput: React.Dispatch>; - handleInference: (input: string) => void; + handleInference: (input: string, files: FileData[]) => void; isStreaming: boolean; isListening: boolean; setIsListening: (isListening: boolean) => void; + files: FileData[]; + setFiles: React.Dispatch>; } export default function InputArea({ @@ -21,15 +36,21 @@ export default function InputArea({ isStreaming, isListening, setIsListening, + files, + setFiles, }: InputAreaProps) { const textareaRef = useRef(null); + const [isFileUploadOpen, setIsFileUploadOpen] = useState(false); + const [isDragging, setIsDragging] = useState(false); + // Preserved original focus logic useEffect(() => { - if (textareaRef.current) { + if (textareaRef.current && !isStreaming) { textareaRef.current.focus(); } }, [isStreaming]); + // Maintained textarea height adjustment useEffect(() => { if (textareaRef.current) { adjustTextareaHeight(); @@ -47,20 +68,126 @@ export default function InputArea({ setTextInput(e.target.value); }; + // Preserved original key press handling const handleKeyPress = (e: React.KeyboardEvent) => { - if (e.key === "Enter" && !e.shiftKey) { + if (e.key === "Enter" && !e.shiftKey && !isStreaming) { e.preventDefault(); - handleInference(textInput); + handleInference(textInput, files); } }; + // Maintained voice input with space addition const handleVoiceInput = (transcript: string) => { - setTextInput((prevInput) => prevInput + " " + transcript); + setTextInput((prevText) => prevText + (prevText ? " " : "") + transcript); + }; + + const handleFileUpload = async (uploadedFiles: File[]) => { + try { + const encodedFiles = await Promise.all( + uploadedFiles.map(async (file) => { + const validation = validateFile(file); + if (!validation.valid) { + throw new Error(validation.error); + } + + const base64 = await encodeFileToBase64(file); + if (isImageFile(file)) { + return { + type: "image_url" as const, + image_url: { url: `data:${file.type};base64,${base64}` }, + name: file.name, + }; + } + return { + type: "text" as const, + text: base64, + name: file.name, + }; + }) + ); + setFiles((prevFiles) => [...prevFiles, ...encodedFiles]); + } catch (error) { + console.error("File upload error:", error); + // Consider adding toast/alert here + } + setIsFileUploadOpen(false); + }; + + const removeFile = (index: number) => { + setFiles((prevFiles) => prevFiles.filter((_, i) => i !== index)); + }; + + // Drag and drop handlers + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + setIsDragging(true); + }; + + const handleDragLeave = () => setIsDragging(false); + + const handleDrop = async (e: React.DragEvent) => { + e.preventDefault(); + setIsDragging(false); + const files = e.dataTransfer?.files; + if (files) handleFileUpload(Array.from(files)); }; return ( -
+
+ {isDragging && ( +
+ Drop files to upload +
+ )} +
+ {/* File preview section */} + {files.length > 0 && ( + <> +
+ {files.map((file, index) => ( +
+
+ {file.type === "image_url" ? ( + {file.name} + ) : ( + + )} +
+ + {file.name} + + +
+ ))} +
+
+ + )} + + {/* Main text input area */}