From 1d5e2cccba12dee0212551fbdc04b5765e9ac948 Mon Sep 17 00:00:00 2001 From: Divyanshu Malik Date: Sat, 11 Oct 2025 19:04:32 -0700 Subject: [PATCH] UI Fixes (#27): added adjustments to chat input and layout --- .gitignore | 3 +- src/app/api/research/chat/route.ts | 51 ++++ src/app/research/components/chat-input.tsx | 41 +++- src/app/research/page.tsx | 272 +++++++++++++-------- 4 files changed, 266 insertions(+), 101 deletions(-) diff --git a/.gitignore b/.gitignore index b3c05d5..b4c76ec 100644 --- a/.gitignore +++ b/.gitignore @@ -46,7 +46,8 @@ next-env.d.ts -# extra +# extra +extra /src/archive /src/experimental parsing-cache.json diff --git a/src/app/api/research/chat/route.ts b/src/app/api/research/chat/route.ts index a72f6f4..eb7720d 100644 --- a/src/app/api/research/chat/route.ts +++ b/src/app/api/research/chat/route.ts @@ -118,6 +118,57 @@ const getErrorMessage = (error: any): string => { return "Failed to process your question"; }; +// CODE FOR TESTING PURPOSES - SIMULATED STREAMING RESPONSE + +// export async function POST(req: NextRequest) { +// try { +// const requestData: RequestData = await req.json(); +// const sessionId = requestData.sessionId || Date.now().toString(); + +// const RANDOM_BOGUS = [ +// "Flibber flabber wozzle!", +// "Gloopity glop, the blockchain bounces!", +// "Snarfle wumpus, quantum bananas dance!", +// "Ziggity zaggity, distributed spaghetti everywhere!", +// "Hocus pocus, your query just became a penguin!" +// ]; + +// const stream = new ReadableStream({ + +// async start(controller) { +// const startTime = Date.now(); +// const fiveMinutes = 1 * 10 * 1000; + +// while (Date.now() - startTime < fiveMinutes) { +// const randomIndex = Math.floor(Math.random() * RANDOM_BOGUS.length); +// const text = RANDOM_BOGUS[randomIndex] + " "; + +// controller.enqueue(text); +// console.log("printing........") +// // Small delay so it feels like typing +// await new Promise((resolve) => setTimeout(resolve, 100)); +// } + +// controller.close(); +// }, +// }); + +// return new Response(stream, { +// headers: { +// "Content-Type": "text/plain; charset=utf-8", +// "X-Session-Id": sessionId, +// }, +// }); +// } catch (error) { +// console.error("Error in chat API:", error); +// return NextResponse.json( +// { error: "Internal server error" }, +// { status: 500 } +// ); +// } +// } + + export async function POST(req: NextRequest) { try { const requestData: RequestData = await req.json(); diff --git a/src/app/research/components/chat-input.tsx b/src/app/research/components/chat-input.tsx index d439fbd..fd31e70 100644 --- a/src/app/research/components/chat-input.tsx +++ b/src/app/research/components/chat-input.tsx @@ -35,6 +35,8 @@ interface ChatInputProps { isPreparingIndex: boolean; selectedDocuments: Document[]; onKeyDown: (e: React.KeyboardEvent) => void; + stopStreaming: () => void; + isStreaming: boolean; } export const ChatInput: React.FC = ({ @@ -45,6 +47,8 @@ export const ChatInput: React.FC = ({ isPreparingIndex, selectedDocuments, onKeyDown, + stopStreaming, + isStreaming, }) => { const { activeTool, setTool } = useTool(); const [language, setLanguage] = useState("ts"); @@ -70,6 +74,7 @@ export const ChatInput: React.FC = ({ }; onSendMessage(payload); + setInputValue(""); }; const handleToolChange = (tool: string) => { @@ -163,7 +168,7 @@ export const ChatInput: React.FC = ({ - + */} +
+ {!isStreaming ? ( + + ) : ( + + )} +
+ diff --git a/src/app/research/page.tsx b/src/app/research/page.tsx index 30a23a9..54cd016 100644 --- a/src/app/research/page.tsx +++ b/src/app/research/page.tsx @@ -50,6 +50,7 @@ import { ChevronLeft, ChevronRight, GlobeIcon, Menu, MessageCircle, SquarePen } import { useRouter, useSearchParams } from "next/navigation"; import { Suspense, useCallback, useEffect, useRef, useState } from "react"; + interface Message { id: string; content: string; @@ -99,7 +100,13 @@ function useSessionId() { return { sessionId: sessionIdRef.current, resetSession }; } + + function ResearchChatPageContent() { + + console.log("ResearchChatPageContent mounted"); + + const modeOptions: Record<"research" | "code", string> = { research: "Research", code: "Code", @@ -112,7 +119,10 @@ function ResearchChatPageContent() { const [selectedDocuments, setSelectedDocuments] = useState([]); const [messages, setMessages] = useState([]); const [inputValue, setInputValue] = useState(""); + + const [isLoading, setIsLoading] = useState(false); + const [isPreparingIndex, setIsPreparingIndex] = useState(false); // eslint-disable-next-line @typescript-eslint/no-unused-vars const [indexError, setIndexError] = useState(false); @@ -124,6 +134,10 @@ function ResearchChatPageContent() { const [mode, setMode] = useState<"research" | "code">("research"); const [scrollOpacity, setScrollOpacity] = useState(0); + const [isStreaming, setIsStreaming] = useState(false); + const [serverResponding, setServerResponding] = useState(false); // ✅ new + const abortControllerRef = useRef(null); + const { sessionId, resetSession } = useSessionId(); const messagesEndRef = useRef(null); @@ -514,101 +528,126 @@ function ResearchChatPageContent() { } }; - const handleSendMessage = async (payload: { - query: string; - documentPaths: string[]; - tool?: string; - language?: Language; - scope?: string[]; - }) => { - const userMessage: Message = { - id: crypto.randomUUID(), - content: payload.query, - role: "user", - timestamp: new Date().toISOString(), - }; + + +const handleStop = () => { + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + abortControllerRef.current = null; + } + setIsStreaming(false); + setServerResponding(false); // ✅ stop reflects server streaming + }; + +// const handleStop = () => { +// console.log("handleStop called"); +// }; + - // Create a placeholder for the assistant's response - const assistantPlaceholderMessage: Message = { - id: crypto.randomUUID(), - content: - payload.tool === "code-composer" - ? "Reading and analyzing documents to generate code. Check the preview panel to see live progress." - : "", - role: "assistant", +// Modify handleSendMessage +const handleSendMessage = async (payload: { + query: string; + documentPaths: string[]; + tool?: string; + language?: Language; + scope?: string[]; +}) => { + + const userMessage: Message = { + id: crypto.randomUUID(), + content: payload.query, + role: "user", + timestamp: new Date().toISOString(), + }; + + const assistantPlaceholderMessage: Message = { + id: crypto.randomUUID(), + content: + payload.tool === "code-composer" + ? "Reading and analyzing documents to generate code. Check the preview panel to see live progress." + : "", + role: "assistant", + timestamp: new Date().toISOString(), + isLoadingPlaceholder: payload.tool !== "code-composer", + docPaths: payload.documentPaths, + }; + + setMessages((prev) => [...prev, userMessage, assistantPlaceholderMessage]); + setInputValue(""); + setIsLoading(true); + setIsStreaming(true); + setServerResponding(true); + + const controller = new AbortController(); + abortControllerRef.current = controller; + + let earlyCodeGenerationId: string | null = null; + if (payload.tool === "code-composer") { + earlyCodeGenerationId = crypto.randomUUID(); + + const newCodeGeneration: CodeGeneration = { + id: earlyCodeGenerationId, + language: payload.language || "ts", + query: payload.query, + topic: "", + plan: "", + pseudocode: "", + implementation: "", + hasStructuredResponse: false, timestamp: new Date().toISOString(), - isLoadingPlaceholder: payload.tool !== "code-composer", - docPaths: payload.documentPaths, + isStreaming: true, + currentSection: "reading-documents", + sources: [], }; - setMessages((prev) => [...prev, userMessage, assistantPlaceholderMessage]); - setInputValue(""); - setIsLoading(true); - - let earlyCodeGenerationId: string | null = null; - if (payload.tool === "code-composer") { - earlyCodeGenerationId = crypto.randomUUID(); - - const newCodeGeneration: CodeGeneration = { - id: earlyCodeGenerationId, - language: payload.language || "ts", - query: payload.query, - topic: "", - plan: "", - pseudocode: "", - implementation: "", - hasStructuredResponse: false, - timestamp: new Date().toISOString(), - isStreaming: true, - currentSection: "reading-documents", - sources: [], - }; - - setCodeGenerations((prev) => [...prev, newCodeGeneration]); - } + setCodeGenerations((prev) => [...prev, newCodeGeneration]); + } - try { - const streamingPayload = { - ...payload, - enableStreaming: true, - sessionId, - }; - const response = await fetch("/api/research/chat", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(streamingPayload), - }); - - if (!response.ok) { - setMessages((prev) => - prev.map((msg) => - msg.id === assistantPlaceholderMessage.id - ? { - ...msg, - content: - "Sorry, I couldn't get a response. Please try again.", - isLoadingPlaceholder: false, - } - : msg, - ), - ); + try { + const streamingPayload = { + ...payload, + enableStreaming: true, + sessionId, + }; + const response = await fetch("/api/research/chat", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(streamingPayload), + signal: controller.signal, + }); - if (earlyCodeGenerationId) { - setCodeGenerations((prev) => - prev.filter((gen) => gen.id !== earlyCodeGenerationId), - ); - } + if (!response.ok) { + setMessages((prev) => + prev.map((msg) => + msg.id === assistantPlaceholderMessage.id + ? { + ...msg, + content: "Sorry, I couldn't get a response. Please try again.", + isLoadingPlaceholder: false, + } + : msg + ) + ); - throw new Error(`Failed to send message. Status: ${response.status}`); + if (earlyCodeGenerationId) { + setCodeGenerations((prev) => + prev.filter((gen) => gen.id !== earlyCodeGenerationId) + ); } - await handleCodeComposerStream( - response, - assistantPlaceholderMessage, - payload, - earlyCodeGenerationId, - ); - } catch (error) { + throw new Error(`Failed to send message. Status: ${response.status}`); + } + + await handleCodeComposerStream( + response, + assistantPlaceholderMessage, + payload, + earlyCodeGenerationId + ); + } catch (error: any) { + if (error.name === "AbortError") { + console.log("Streaming stopped by user"); + } else { console.error("Chat error:", error); setMessages((prev) => prev.map((msg) => @@ -618,19 +657,24 @@ function ResearchChatPageContent() { content: "Sorry, an error occurred. Please try again.", isLoadingPlaceholder: false, } - : msg, - ), + : msg + ) ); + } - if (earlyCodeGenerationId) { - setCodeGenerations((prev) => - prev.filter((gen) => gen.id !== earlyCodeGenerationId), - ); - } - } finally { - setIsLoading(false); + if (earlyCodeGenerationId) { + setCodeGenerations((prev) => + prev.filter((gen) => gen.id !== earlyCodeGenerationId) + ); } - }; + } finally { + setIsLoading(false); + setIsStreaming(false); + abortControllerRef.current = null; + } +}; + + const handleKeyDown = () => {}; @@ -1060,7 +1104,7 @@ function ResearchChatPageContent() { Search - + /> */} + { + if (isStreaming) { + handleStop(); // 🔴 stop streaming if already running + } else { + handleSendMessage({ + query: inputValue, + documentPaths: selectedDocuments.map((d) => d.path), + tool: activeTool === "code-composer" ? "code-composer" : undefined, + language: language, + }); + } + }} + > + {/* {isStreaming ? "■ Stop" : "▶️ Chat"} */} + +