From 78e14e670f0eb18ffca6b4f7dfd6fb2b037b162f Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 1 Jul 2025 21:00:56 +0200 Subject: [PATCH 1/5] "Fixes" --- Makefile | 33 ++++++++++++++---- frontend/src/components/InputForm.tsx | 2 +- install_deps.bat | 48 +++++++++++++++++++++++++++ start_dev_servers.bat | 21 ++++++++++++ start_dev_servers_no_langsmith.bat | 21 ++++++++++++ 5 files changed, 117 insertions(+), 8 deletions(-) create mode 100644 install_deps.bat create mode 100644 start_dev_servers.bat create mode 100644 start_dev_servers_no_langsmith.bat diff --git a/Makefile b/Makefile index 2e5c9033..bfd810c5 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,27 @@ -.PHONY: help dev-frontend dev-backend dev +.PHONY: help install install-backend install-frontend dev-frontend dev-backend dev help: @echo "Available commands:" - @echo " make dev-frontend - Starts the frontend development server (Vite)" - @echo " make dev-backend - Starts the backend development server (Uvicorn with reload)" - @echo " make dev - Starts both frontend and backend development servers" + @echo " make install - Installs all dependencies for Windows" + @echo " make dev - Starts both frontend and backend servers for Windows" + @echo " ---" + @echo " make install-backend - Installs backend dependencies" + @echo " make install-frontend - Installs frontend dependencies" + @echo " make dev-frontend - Starts the frontend development server (Vite)" + @echo " make dev-backend - Starts the backend development server (Langgraph)" + + +install: install-backend install-frontend + +install-backend: + @echo "Checking for backend virtual environment..." + @if not exist backend\venv (py -m venv backend\venv) + @echo "Installing backend dependencies..." + @call backend\venv\Scripts\activate.bat && pip install -e backend + +install-frontend: + @echo "Installing frontend dependencies..." + @cd frontend && npm install dev-frontend: @echo "Starting frontend development server..." @@ -12,9 +29,11 @@ dev-frontend: dev-backend: @echo "Starting backend development server..." - @cd backend && langgraph dev + @echo "Activating backend environment and starting server..." + @cd backend && call .\venv\Scripts\activate.bat && langgraph dev # Run frontend and backend concurrently dev: - @echo "Starting both frontend and backend development servers..." - @make dev-frontend & make dev-backend \ No newline at end of file + @echo "Starting both frontend and backend development servers in new windows..." + @start "Frontend" make dev-frontend + @start "Backend" make dev-backend \ No newline at end of file diff --git a/frontend/src/components/InputForm.tsx b/frontend/src/components/InputForm.tsx index 97aa5c67..2e5e3162 100644 --- a/frontend/src/components/InputForm.tsx +++ b/frontend/src/components/InputForm.tsx @@ -25,7 +25,7 @@ export const InputForm: React.FC = ({ hasHistory, }) => { const [internalInputValue, setInternalInputValue] = useState(""); - const [effort, setEffort] = useState("medium"); + const [effort, setEffort] = useState("high"); const [model, setModel] = useState("gemini-2.5-flash-preview-04-17"); const handleInternalSubmit = (e?: React.FormEvent) => { diff --git a/install_deps.bat b/install_deps.bat new file mode 100644 index 00000000..e9f624ec --- /dev/null +++ b/install_deps.bat @@ -0,0 +1,48 @@ +@echo off +cd /d "%~dp0" + +echo. +echo ================================================================= +echo Installing Dependencies +echo ================================================================= +echo. + +REM 1. Create and install backend dependencies +if not exist "backend\venv\Scripts\activate.bat" ( + echo [INFO] Creating backend virtual environment... + py -3.11 -m venv backend\venv + if errorlevel 1 ( + echo [ERROR] Failed to create backend virtual environment. + pause + exit /b 1 + ) +) +echo [INFO] Installing backend dependencies... +call backend\venv\Scripts\activate.bat +pip install -e backend +if errorlevel 1 ( + echo [ERROR] Failed to install backend dependencies. + pause + exit /b 1 +) + +REM 2. Install frontend dependencies +if not exist "frontend\node_modules" ( + echo [INFO] Installing frontend dependencies... + pushd frontend + call npm install + if errorlevel 1 ( + echo [ERROR] Failed to install frontend dependencies. + popd + pause + exit /b 1 + ) + popd +) + +echo. +echo ================================================================= +echo Dependency installation complete. +echo ================================================================= +echo. +pause diff --git a/start_dev_servers.bat b/start_dev_servers.bat new file mode 100644 index 00000000..dfe8bf11 --- /dev/null +++ b/start_dev_servers.bat @@ -0,0 +1,21 @@ +@echo off +cd /d "%~dp0" + +if not exist "backend\venv\Scripts\activate.bat" ( + echo [ERROR] Backend dependencies not found. Please run install_deps.bat first. + pause + exit /b 1 +) + +if not exist "frontend\node_modules" ( + echo [ERROR] Frontend dependencies not found. Please run install_deps.bat first. + pause + exit /b 1 +) + +echo [INFO] Starting servers... +start "Backend" cmd /c "cd /d "%~dp0\backend" && .\venv\Scripts\activate.bat && langgraph dev" +start "Frontend" cmd /c "cd /d "%~dp0\frontend" && npm run dev" + +timeout /t 15 >nul +start http://localhost:5173/app/ \ No newline at end of file diff --git a/start_dev_servers_no_langsmith.bat b/start_dev_servers_no_langsmith.bat new file mode 100644 index 00000000..bbc6ae99 --- /dev/null +++ b/start_dev_servers_no_langsmith.bat @@ -0,0 +1,21 @@ +@echo off +cd /d "%~dp0" + +if not exist "backend\venv\Scripts\activate.bat" ( + echo [ERROR] Backend dependencies not found. Please run install_deps.bat first. + pause + exit /b 1 +) + +if not exist "frontend\node_modules" ( + echo [ERROR] Frontend dependencies not found. Please run install_deps.bat first. + pause + exit /b 1 +) + +echo [INFO] Starting servers... +start "Backend" cmd /c "cd /d "%~dp0\backend" && .\venv\Scripts\activate.bat && langgraph dev --no-browser" +start "Frontend" cmd /c "cd /d "%~dp0\frontend" && npm run dev" + +timeout /t 15 >nul +start http://localhost:5173/app/ \ No newline at end of file From 3b178134425053f8f102c5edd6e4c5e45f2f9b2a Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 1 Jul 2025 22:27:15 +0200 Subject: [PATCH 2/5] add hystory panel, backup folder and orig folder --- backup/App.tsx.bak | 232 ++++++++++++++++ backup/App.tsx.bak2 | 236 +++++++++++++++++ backup/ChatMessagesView.tsx.bak | 322 +++++++++++++++++++++++ backup/HistoryPanel.tsx.bak | 36 +++ backup/WelcomeScreen.tsx.bak | 39 +++ frontend/src/App.tsx | 161 ++++++++---- frontend/src/components/HistoryPanel.tsx | 32 +++ orig/App.tsx | 189 +++++++++++++ 8 files changed, 1190 insertions(+), 57 deletions(-) create mode 100644 backup/App.tsx.bak create mode 100644 backup/App.tsx.bak2 create mode 100644 backup/ChatMessagesView.tsx.bak create mode 100644 backup/HistoryPanel.tsx.bak create mode 100644 backup/WelcomeScreen.tsx.bak create mode 100644 frontend/src/components/HistoryPanel.tsx create mode 100644 orig/App.tsx diff --git a/backup/App.tsx.bak b/backup/App.tsx.bak new file mode 100644 index 00000000..d2933008 --- /dev/null +++ b/backup/App.tsx.bak @@ -0,0 +1,232 @@ +import { useStream } from "@langchain/langgraph-sdk/react"; +import type { Message } from "@langchain/langgraph-sdk"; +import { useState, useEffect, useRef, useCallback } from "react"; +import { ProcessedEvent } from "@/components/ActivityTimeline"; +import { WelcomeScreen } from "@/components/WelcomeScreen"; +import { ChatMessagesView } from "@/components/ChatMessagesView"; +import { HistoryPanel } from "@/components/HistoryPanel"; + +const API_URL = import.meta.env.DEV + ? "http://localhost:2024" + : "http://localhost:8123"; + +export default function App() { + const [processedEventsTimeline, setProcessedEventsTimeline] = useState< + ProcessedEvent[] + >([]); + const [historicalActivities, setHistoricalActivities] = useState< + Record + >({}); + const scrollAreaRef = useRef(null); + const hasFinalizeEventOccurredRef = useRef(false); + const [history, setHistory] = useState([]); + const [threadId, setThreadId] = useState(null); + const [messages, setMessages] = useState([]); + const thread = useStream<{ + messages: Message[]; + initial_search_query_count: number; + max_research_loops: number; + reasoning_model: string; + }>({ + apiUrl: API_URL, + assistantId: "agent", + threadId, + onThreadId: setThreadId, + messagesKey: "messages", + onUpdateEvent: (event: any) => { + let processedEvent: ProcessedEvent | null = null; + if (event.generate_query) { + processedEvent = { + title: "Generating Search Queries", + data: event.generate_query?.search_query?.join(", ") || "", + }; + } else if (event.web_research) { + const sources = event.web_research.sources_gathered || []; + const numSources = sources.length; + const uniqueLabels = [ + ...new Set(sources.map((s: any) => s.label).filter(Boolean)), + ]; + const exampleLabels = uniqueLabels.slice(0, 3).join(", "); + processedEvent = { + title: "Web Research", + data: `Gathered ${numSources} sources. Related to: ${ + exampleLabels || "N/A" + }.`, + }; + } else if (event.reflection) { + processedEvent = { + title: "Reflection", + data: "Analysing Web Research Results", + }; + } else if (event.finalize_answer) { + processedEvent = { + title: "Finalizing Answer", + data: "Composing and presenting the final answer.", + }; + hasFinalizeEventOccurredRef.current = true; + } + if (processedEvent) { + setProcessedEventsTimeline((prevEvents) => [ + ...prevEvents, + processedEvent!, + ]); + } + }, + onFinish: async (threadState) => { + if (threadId) { + const lastMessage = + threadState.values.messages[ + threadState.values.messages.length - 1 + ]; + let updatedHistoricalActivities = { ...historicalActivities }; + if (lastMessage && lastMessage.type === "ai" && lastMessage.id) { + updatedHistoricalActivities = { + ...historicalActivities, + [lastMessage.id]: [...processedEventsTimeline], + }; + setHistoricalActivities(updatedHistoricalActivities); + } + + const newHistoryItem = { + session_id: threadId, + messages: threadState.values.messages, + historicalActivities: updatedHistoricalActivities, + }; + + try { + await fetch(`${API_URL}/sessions`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(newHistoryItem), + }); + setHistory((prevHistory) => [newHistoryItem, ...prevHistory]); + } catch (error) { + console.error("Failed to save history:", error); + } + } + }, + onError: (error: any) => { + console.error(error); + }, + }); + + useEffect(() => { + async function fetchHistory() { + try { + const response = await fetch(`${API_URL}/sessions`); + const data = await response.json(); + setHistory(data); + } catch (error) { + console.error("Failed to fetch history:", error); + } + } + fetchHistory(); + }, []); + + useEffect(() => { + if (scrollAreaRef.current) { + const scrollViewport = scrollAreaRef.current.querySelector( + "[data-radix-scroll-area-viewport]" + ); + if (scrollViewport) { + scrollViewport.scrollTop = scrollViewport.scrollHeight; + } + } + }, [messages]); + + useEffect(() => { + if (thread.messages.length > messages.length) { + setMessages(thread.messages); + } + }, [thread.messages, messages]); + + const handleSelectHistory = (historyItem: any) => { + setThreadId(historyItem.session_id); + setMessages(historyItem.messages); + setHistoricalActivities(historyItem.historicalActivities || {}); + setProcessedEventsTimeline([]); + }; + + const handleSubmit = useCallback( + async (submittedInputValue: string, effort: string, model: string) => { + if (!submittedInputValue.trim() || thread.isLoading) return; + setProcessedEventsTimeline([]); + hasFinalizeEventOccurredRef.current = false; + if (!threadId) { + setThreadId(new Date().toISOString()); + } + + // convert effort to, initial_search_query_count and max_research_loops + // low means max 1 loop and 1 query + // medium means max 3 loops and 3 queries + // high means max 10 loops and 5 queries + let initial_search_query_count = 0; + let max_research_loops = 0; + switch (effort) { + case "low": + initial_search_query_count = 1; + max_research_loops = 1; + break; + case "medium": + initial_search_query_count = 3; + max_research_loops = 3; + break; + case "high": + initial_search_query_count = 5; + max_research_loops = 10; + break; + } + + const newMessages: Message[] = [ + ...messages, + { + type: "human", + content: submittedInputValue, + id: Date.now().toString(), + }, + ]; + setMessages(newMessages); + thread.submit({ + messages: newMessages, + initial_search_query_count: initial_search_query_count, + max_research_loops: max_research_loops, + reasoning_model: model, + }); + }, + [thread, threadId, messages] + ); + + const handleCancel = useCallback(() => { + thread.stop(); + }, [thread]); + + return ( +
+ +
+ {messages.length === 0 ? ( + + ) : ( + + )} +
+
+ ); +} \ No newline at end of file diff --git a/backup/App.tsx.bak2 b/backup/App.tsx.bak2 new file mode 100644 index 00000000..c6ee2a6c --- /dev/null +++ b/backup/App.tsx.bak2 @@ -0,0 +1,236 @@ +import { useStream } from "@langchain/langgraph-sdk/react"; +import type { Message } from "@langchain/langgraph-sdk"; +import { useState, useEffect, useRef, useCallback } from "react"; +import { ProcessedEvent } from "@/components/ActivityTimeline"; +import { WelcomeScreen } from "@/components/WelcomeScreen"; +import { ChatMessagesView } from "@/components/ChatMessagesView"; +import { HistoryPanel } from "@/components/HistoryPanel"; + +const API_URL = import.meta.env.DEV + ? "http://localhost:2024" + : "http://localhost:8123"; + +export default function App() { + const [processedEventsTimeline, setProcessedEventsTimeline] = useState< + ProcessedEvent[] + >([]); + const [historicalActivities, setHistoricalActivities] = useState< + Record + >({}); + const scrollAreaRef = useRef(null); + const hasFinalizeEventOccurredRef = useRef(false); + const [history, setHistory] = useState([]); + const [threadId, setThreadId] = useState(null); + const [messages, setMessages] = useState([]); + const [streamingCompleted, setStreamingCompleted] = useState(false); + + const thread = useStream<{ + messages: Message[]; + initial_search_query_count: number; + max_research_loops: number; + reasoning_model: string; + }>({ + apiUrl: API_URL, + assistantId: "agent", + threadId, + onThreadId: setThreadId, + messagesKey: "messages", + onUpdateEvent: (event: any) => { + let processedEvent: ProcessedEvent | null = null; + if (event.generate_query) { + processedEvent = { + title: "Generating Search Queries", + data: event.generate_query?.search_query?.join(", ") || "", + }; + } else if (event.web_research) { + const sources = event.web_research.sources_gathered || []; + const numSources = sources.length; + const uniqueLabels = [ + ...new Set(sources.map((s: any) => s.label).filter(Boolean)), + ]; + const exampleLabels = uniqueLabels.slice(0, 3).join(", "); + processedEvent = { + title: "Web Research", + data: `Gathered ${numSources} sources. Related to: ${ + exampleLabels || "N/A" + }.`, + }; + } else if (event.reflection) { + processedEvent = { + title: "Reflection", + data: "Analysing Web Research Results", + }; + } else if (event.finalize_answer) { + processedEvent = { + title: "Finalizing Answer", + data: "Composing and presenting the final answer.", + }; + hasFinalizeEventOccurredRef.current = true; + } + if (processedEvent) { + setProcessedEventsTimeline((prevEvents) => [ + ...prevEvents, + processedEvent!, + ]); + } + }, + onFinish: async (threadState) => { + if (threadId) { + const lastMessage = + threadState.values.messages[ + threadState.values.messages.length - 1 + ]; + let updatedHistoricalActivities = { ...historicalActivities }; + if (lastMessage && lastMessage.type === "ai" && lastMessage.id) { + updatedHistoricalActivities = { + ...historicalActivities, + [lastMessage.id]: [...processedEventsTimeline], + }; + setHistoricalActivities(updatedHistoricalActivities); + } + + const newHistoryItem = { + session_id: threadId, + messages: threadState.values.messages, + historicalActivities: updatedHistoricalActivities, + }; + + try { + await fetch(`${API_URL}/sessions`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(newHistoryItem), + }); + setHistory((prevHistory) => [newHistoryItem, ...prevHistory]); + setStreamingCompleted(true); + } catch (error) { + console.error("Failed to save history:", error); + } + } + }, + onError: (error: any) => { + console.error(error); + }, + }); + + useEffect(() => { + async function fetchHistory() { + try { + const response = await fetch(`${API_URL}/sessions`); + const data = await response.json(); + setHistory(data); + } catch (error) { + console.error("Failed to fetch history:", error); + } + } + fetchHistory(); + }, []); + + useEffect(() => { + if (scrollAreaRef.current) { + const scrollViewport = scrollAreaRef.current.querySelector( + "[data-radix-scroll-area-viewport]" + ); + if (scrollViewport) { + scrollViewport.scrollTop = scrollViewport.scrollHeight; + } + } + }, [messages]); + + useEffect(() => { + if (streamingCompleted) { + setMessages(thread.messages); + setStreamingCompleted(false); + } + }, [streamingCompleted, thread.messages]); + + const handleSelectHistory = (historyItem: any) => { + setThreadId(historyItem.session_id); + setMessages(historyItem.messages); + setHistoricalActivities(historyItem.historicalActivities || {}); + setProcessedEventsTimeline([]); + }; + + const handleSubmit = useCallback( + async (submittedInputValue: string, effort: string, model: string) => { + if (!submittedInputValue.trim() || thread.isLoading) return; + setProcessedEventsTimeline([]); + hasFinalizeEventOccurredRef.current = false; + if (!threadId) { + setThreadId(new Date().toISOString()); + } + + // convert effort to, initial_search_query_count and max_research_loops + // low means max 1 loop and 1 query + // medium means max 3 loops and 3 queries + // high means max 10 loops and 5 queries + let initial_search_query_count = 0; + let max_research_loops = 0; + switch (effort) { + case "low": + initial_search_query_count = 1; + max_research_loops = 1; + break; + case "medium": + initial_search_query_count = 3; + max_research_loops = 3; + break; + case "high": + initial_search_query_count = 5; + max_research_loops = 10; + break; + } + + const newMessages: Message[] = [ + ...messages, + { + type: "human", + content: submittedInputValue, + id: Date.now().toString(), + }, + ]; + setMessages(newMessages); + thread.submit({ + messages: newMessages, + initial_search_query_count: initial_search_query_count, + max_research_loops: max_research_loops, + reasoning_model: model, + }); + }, + [thread, threadId, messages] + ); + + const handleCancel = useCallback(() => { + thread.stop(); + }, [thread]); + + return ( +
+ +
+ {messages.length === 0 ? ( + + ) : ( + + )} +
+
+ ); +} \ No newline at end of file diff --git a/backup/ChatMessagesView.tsx.bak b/backup/ChatMessagesView.tsx.bak new file mode 100644 index 00000000..1a245d88 --- /dev/null +++ b/backup/ChatMessagesView.tsx.bak @@ -0,0 +1,322 @@ +import type React from "react"; +import type { Message } from "@langchain/langgraph-sdk"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Loader2, Copy, CopyCheck } from "lucide-react"; +import { InputForm } from "@/components/InputForm"; +import { Button } from "@/components/ui/button"; +import { useState, ReactNode } from "react"; +import ReactMarkdown from "react-markdown"; +import { cn } from "@/lib/utils"; +import { Badge } from "@/components/ui/badge"; +import { + ActivityTimeline, + ProcessedEvent, +} from "@/components/ActivityTimeline"; // Assuming ActivityTimeline is in the same dir or adjust path + +// Markdown component props type from former ReportView +type MdComponentProps = { + className?: string; + children?: ReactNode; + [key: string]: any; +}; + +// Markdown components (from former ReportView.tsx) +const mdComponents = { + h1: ({ className, children, ...props }: MdComponentProps) => ( +

+ {children} +

+ ), + h2: ({ className, children, ...props }: MdComponentProps) => ( +

+ {children} +

+ ), + h3: ({ className, children, ...props }: MdComponentProps) => ( +

+ {children} +

+ ), + p: ({ className, children, ...props }: MdComponentProps) => ( +

+ {children} +

+ ), + a: ({ className, children, href, ...props }: MdComponentProps) => ( + + + {children} + + + ), + ul: ({ className, children, ...props }: MdComponentProps) => ( +
    + {children} +
+ ), + ol: ({ className, children, ...props }: MdComponentProps) => ( +
    + {children} +
+ ), + li: ({ className, children, ...props }: MdComponentProps) => ( +
  • + {children} +
  • + ), + blockquote: ({ className, children, ...props }: MdComponentProps) => ( +
    + {children} +
    + ), + code: ({ className, children, ...props }: MdComponentProps) => ( + + {children} + + ), + pre: ({ className, children, ...props }: MdComponentProps) => ( +
    +      {children}
    +    
    + ), + hr: ({ className, ...props }: MdComponentProps) => ( +
    + ), + table: ({ className, children, ...props }: MdComponentProps) => ( +
    + + {children} +
    +
    + ), + th: ({ className, children, ...props }: MdComponentProps) => ( + + {children} + + ), + td: ({ className, children, ...props }: MdComponentProps) => ( + + {children} + + ), +}; + +// Props for HumanMessageBubble +interface HumanMessageBubbleProps { + message: Message; + mdComponents: typeof mdComponents; +} + +// HumanMessageBubble Component +const HumanMessageBubble: React.FC = ({ + message, + mdComponents, +}) => { + return ( +
    + + {typeof message.content === "string" + ? message.content + : JSON.stringify(message.content)} + +
    + ); +}; + +// Props for AiMessageBubble +interface AiMessageBubbleProps { + message: Message; + historicalActivity: ProcessedEvent[] | undefined; + liveActivity: ProcessedEvent[] | undefined; + isLastMessage: boolean; + isOverallLoading: boolean; + mdComponents: typeof mdComponents; + handleCopy: (text: string, messageId: string) => void; + copiedMessageId: string | null; +} + +// AiMessageBubble Component +const AiMessageBubble: React.FC = ({ + message, + historicalActivity, + liveActivity, + isLastMessage, + isOverallLoading, + mdComponents, + handleCopy, + copiedMessageId, +}) => { + // Determine which activity events to show and if it's for a live loading message + const activityForThisBubble = + isLastMessage && isOverallLoading ? liveActivity : historicalActivity; + const isLiveActivityForThisBubble = isLastMessage && isOverallLoading; + + return ( +
    + {activityForThisBubble && activityForThisBubble.length > 0 && ( +
    + +
    + )} + + {typeof message.content === "string" + ? message.content + : JSON.stringify(message.content)} + + +
    + ); +}; + +interface ChatMessagesViewProps { + messages: Message[]; + isLoading: boolean; + scrollAreaRef: React.RefObject; + onSubmit: (inputValue: string, effort: string, model: string) => void; + onCancel: () => void; + liveActivityEvents: ProcessedEvent[]; + historicalActivities: Record; +} + +export function ChatMessagesView({ + messages, + isLoading, + scrollAreaRef, + onSubmit, + onCancel, + liveActivityEvents, + historicalActivities, +}: ChatMessagesViewProps) { + const [copiedMessageId, setCopiedMessageId] = useState(null); + + const handleCopy = async (text: string, messageId: string) => { + try { + await navigator.clipboard.writeText(text); + setCopiedMessageId(messageId); + setTimeout(() => setCopiedMessageId(null), 2000); // Reset after 2 seconds + } catch (err) { + console.error("Failed to copy text: ", err); + } + }; + return ( +
    + +
    + {messages.map((message, index) => { + const isLast = index === messages.length - 1; + return ( +
    +
    + {message.type === "human" ? ( + + ) : ( + + )} +
    +
    + ); + })} + {isLoading && + (messages.length === 0 || + messages[messages.length - 1].type === "human") && ( +
    + {" "} + {/* AI message row structure */} +
    + {liveActivityEvents.length > 0 ? ( +
    + +
    + ) : ( +
    + + Processing... +
    + )} +
    +
    + )} +
    +
    + 0} + /> +
    + ); +} diff --git a/backup/HistoryPanel.tsx.bak b/backup/HistoryPanel.tsx.bak new file mode 100644 index 00000000..0adf7347 --- /dev/null +++ b/backup/HistoryPanel.tsx.bak @@ -0,0 +1,36 @@ +import React from 'react'; + +interface HistoryPanelProps { + history: any[]; + onSelectHistory: (historyItem: any) => void; +} + +export const HistoryPanel: React.FC = ({ + history, + onSelectHistory, +}) => { + return ( +
    +

    History

    +
    + {history.map((historyItem, index) => ( +
    onSelectHistory(historyItem)} + > +

    + {historyItem.messages[0]?.content} +

    +

    + {new Date(historyItem.session_id).toLocaleString()} +

    +
    + ))} +
    +
    + ); +}; diff --git a/backup/WelcomeScreen.tsx.bak b/backup/WelcomeScreen.tsx.bak new file mode 100644 index 00000000..b1015aa8 --- /dev/null +++ b/backup/WelcomeScreen.tsx.bak @@ -0,0 +1,39 @@ +import { InputForm } from "./InputForm"; + +interface WelcomeScreenProps { + handleSubmit: ( + submittedInputValue: string, + effort: string, + model: string + ) => void; + onCancel: () => void; + isLoading: boolean; +} + +export const WelcomeScreen: React.FC = ({ + handleSubmit, + onCancel, + isLoading, +}) => ( +
    +
    +

    + Welcome. +

    +

    + How can I help you today? +

    +
    +
    + +
    +

    + Powered by Google Gemini and LangChain LangGraph. +

    +
    +); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index d06d4021..bf4e6ebb 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -4,7 +4,11 @@ import { useState, useEffect, useRef, useCallback } from "react"; import { ProcessedEvent } from "@/components/ActivityTimeline"; import { WelcomeScreen } from "@/components/WelcomeScreen"; import { ChatMessagesView } from "@/components/ChatMessagesView"; -import { Button } from "@/components/ui/button"; +import { HistoryPanel } from "@/components/HistoryPanel"; + +const API_URL = import.meta.env.DEV + ? "http://localhost:2024" + : "http://localhost:8123"; export default function App() { const [processedEventsTimeline, setProcessedEventsTimeline] = useState< @@ -15,17 +19,21 @@ export default function App() { >({}); const scrollAreaRef = useRef(null); const hasFinalizeEventOccurredRef = useRef(false); - const [error, setError] = useState(null); + const [history, setHistory] = useState([]); + const [threadId, setThreadId] = useState(null); + const [messages, setMessages] = useState([]); + const [streamingCompleted, setStreamingCompleted] = useState(false); + const thread = useStream<{ messages: Message[]; initial_search_query_count: number; max_research_loops: number; reasoning_model: string; }>({ - apiUrl: import.meta.env.DEV - ? "http://localhost:2024" - : "http://localhost:8123", + apiUrl: API_URL, assistantId: "agent", + threadId, + onThreadId: setThreadId, messagesKey: "messages", onUpdateEvent: (event: any) => { let processedEvent: ProcessedEvent | null = null; @@ -66,11 +74,60 @@ export default function App() { ]); } }, + onFinish: async (threadState) => { + if (threadId) { + const lastMessage = + threadState.values.messages[ + threadState.values.messages.length - 1 + ]; + let updatedHistoricalActivities = { ...historicalActivities }; + if (lastMessage && lastMessage.type === "ai" && lastMessage.id) { + updatedHistoricalActivities = { + ...historicalActivities, + [lastMessage.id]: [...processedEventsTimeline], + }; + setHistoricalActivities(updatedHistoricalActivities); + } + + const newHistoryItem = { + session_id: threadId, + messages: threadState.values.messages, + historicalActivities: updatedHistoricalActivities, + }; + + try { + await fetch(`${API_URL}/sessions`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(newHistoryItem), + }); + setHistory((prevHistory) => [newHistoryItem, ...prevHistory]); + setStreamingCompleted(true); + } catch (error) { + console.error("Failed to save history:", error); + } + } + }, onError: (error: any) => { - setError(error.message); + console.error(error); }, }); + useEffect(() => { + async function fetchHistory() { + try { + const response = await fetch(`${API_URL}/sessions`); + const data = await response.json(); + setHistory(data); + } catch (error) { + console.error("Failed to fetch history:", error); + } + } + fetchHistory(); + }, []); + useEffect(() => { if (scrollAreaRef.current) { const scrollViewport = scrollAreaRef.current.querySelector( @@ -80,30 +137,30 @@ export default function App() { scrollViewport.scrollTop = scrollViewport.scrollHeight; } } - }, [thread.messages]); + }, [messages]); useEffect(() => { - if ( - hasFinalizeEventOccurredRef.current && - !thread.isLoading && - thread.messages.length > 0 - ) { - const lastMessage = thread.messages[thread.messages.length - 1]; - if (lastMessage && lastMessage.type === "ai" && lastMessage.id) { - setHistoricalActivities((prev) => ({ - ...prev, - [lastMessage.id!]: [...processedEventsTimeline], - })); - } - hasFinalizeEventOccurredRef.current = false; + if (streamingCompleted) { + setMessages(thread.messages); + setStreamingCompleted(false); } - }, [thread.messages, thread.isLoading, processedEventsTimeline]); + }, [streamingCompleted, thread.messages]); + + const handleSelectHistory = (historyItem: any) => { + setThreadId(historyItem.session_id); + setMessages(historyItem.messages); + setHistoricalActivities(historyItem.historicalActivities || {}); + setProcessedEventsTimeline([]); + }; const handleSubmit = useCallback( - (submittedInputValue: string, effort: string, model: string) => { - if (!submittedInputValue.trim()) return; + async (submittedInputValue: string, effort: string, model: string) => { + if (!submittedInputValue.trim() || thread.isLoading) return; setProcessedEventsTimeline([]); hasFinalizeEventOccurredRef.current = false; + if (!threadId) { + setThreadId(new Date().toISOString()); + } // convert effort to, initial_search_query_count and max_research_loops // low means max 1 loop and 1 query @@ -127,13 +184,14 @@ export default function App() { } const newMessages: Message[] = [ - ...(thread.messages || []), + ...messages, { type: "human", content: submittedInputValue, id: Date.now().toString(), }, ]; + setMessages(newMessages); thread.submit({ messages: newMessages, initial_search_query_count: initial_search_query_count, @@ -141,48 +199,37 @@ export default function App() { reasoning_model: model, }); }, - [thread] + [thread, threadId, messages] ); const handleCancel = useCallback(() => { thread.stop(); - window.location.reload(); }, [thread]); return (
    +
    - {thread.messages.length === 0 ? ( - - ) : error ? ( -
    -
    -

    Error

    -

    {JSON.stringify(error)}

    - - -
    -
    - ) : ( - - )} + {messages.length === 0 ? ( + + ) : ( + + )}
    ); diff --git a/frontend/src/components/HistoryPanel.tsx b/frontend/src/components/HistoryPanel.tsx new file mode 100644 index 00000000..5d2ca28a --- /dev/null +++ b/frontend/src/components/HistoryPanel.tsx @@ -0,0 +1,32 @@ +import { Button } from "@/components/ui/button"; +import { ScrollArea } from "@/components/ui/scroll-area"; + +interface HistoryPanelProps { + history: any[]; + onSelectHistory: (historyItem: any) => void; +} + +export const HistoryPanel: React.FC = ({ + history, + onSelectHistory, +}) => { + return ( + + ); +}; diff --git a/orig/App.tsx b/orig/App.tsx new file mode 100644 index 00000000..d06d4021 --- /dev/null +++ b/orig/App.tsx @@ -0,0 +1,189 @@ +import { useStream } from "@langchain/langgraph-sdk/react"; +import type { Message } from "@langchain/langgraph-sdk"; +import { useState, useEffect, useRef, useCallback } from "react"; +import { ProcessedEvent } from "@/components/ActivityTimeline"; +import { WelcomeScreen } from "@/components/WelcomeScreen"; +import { ChatMessagesView } from "@/components/ChatMessagesView"; +import { Button } from "@/components/ui/button"; + +export default function App() { + const [processedEventsTimeline, setProcessedEventsTimeline] = useState< + ProcessedEvent[] + >([]); + const [historicalActivities, setHistoricalActivities] = useState< + Record + >({}); + const scrollAreaRef = useRef(null); + const hasFinalizeEventOccurredRef = useRef(false); + const [error, setError] = useState(null); + const thread = useStream<{ + messages: Message[]; + initial_search_query_count: number; + max_research_loops: number; + reasoning_model: string; + }>({ + apiUrl: import.meta.env.DEV + ? "http://localhost:2024" + : "http://localhost:8123", + assistantId: "agent", + messagesKey: "messages", + onUpdateEvent: (event: any) => { + let processedEvent: ProcessedEvent | null = null; + if (event.generate_query) { + processedEvent = { + title: "Generating Search Queries", + data: event.generate_query?.search_query?.join(", ") || "", + }; + } else if (event.web_research) { + const sources = event.web_research.sources_gathered || []; + const numSources = sources.length; + const uniqueLabels = [ + ...new Set(sources.map((s: any) => s.label).filter(Boolean)), + ]; + const exampleLabels = uniqueLabels.slice(0, 3).join(", "); + processedEvent = { + title: "Web Research", + data: `Gathered ${numSources} sources. Related to: ${ + exampleLabels || "N/A" + }.`, + }; + } else if (event.reflection) { + processedEvent = { + title: "Reflection", + data: "Analysing Web Research Results", + }; + } else if (event.finalize_answer) { + processedEvent = { + title: "Finalizing Answer", + data: "Composing and presenting the final answer.", + }; + hasFinalizeEventOccurredRef.current = true; + } + if (processedEvent) { + setProcessedEventsTimeline((prevEvents) => [ + ...prevEvents, + processedEvent!, + ]); + } + }, + onError: (error: any) => { + setError(error.message); + }, + }); + + useEffect(() => { + if (scrollAreaRef.current) { + const scrollViewport = scrollAreaRef.current.querySelector( + "[data-radix-scroll-area-viewport]" + ); + if (scrollViewport) { + scrollViewport.scrollTop = scrollViewport.scrollHeight; + } + } + }, [thread.messages]); + + useEffect(() => { + if ( + hasFinalizeEventOccurredRef.current && + !thread.isLoading && + thread.messages.length > 0 + ) { + const lastMessage = thread.messages[thread.messages.length - 1]; + if (lastMessage && lastMessage.type === "ai" && lastMessage.id) { + setHistoricalActivities((prev) => ({ + ...prev, + [lastMessage.id!]: [...processedEventsTimeline], + })); + } + hasFinalizeEventOccurredRef.current = false; + } + }, [thread.messages, thread.isLoading, processedEventsTimeline]); + + const handleSubmit = useCallback( + (submittedInputValue: string, effort: string, model: string) => { + if (!submittedInputValue.trim()) return; + setProcessedEventsTimeline([]); + hasFinalizeEventOccurredRef.current = false; + + // convert effort to, initial_search_query_count and max_research_loops + // low means max 1 loop and 1 query + // medium means max 3 loops and 3 queries + // high means max 10 loops and 5 queries + let initial_search_query_count = 0; + let max_research_loops = 0; + switch (effort) { + case "low": + initial_search_query_count = 1; + max_research_loops = 1; + break; + case "medium": + initial_search_query_count = 3; + max_research_loops = 3; + break; + case "high": + initial_search_query_count = 5; + max_research_loops = 10; + break; + } + + const newMessages: Message[] = [ + ...(thread.messages || []), + { + type: "human", + content: submittedInputValue, + id: Date.now().toString(), + }, + ]; + thread.submit({ + messages: newMessages, + initial_search_query_count: initial_search_query_count, + max_research_loops: max_research_loops, + reasoning_model: model, + }); + }, + [thread] + ); + + const handleCancel = useCallback(() => { + thread.stop(); + window.location.reload(); + }, [thread]); + + return ( +
    +
    + {thread.messages.length === 0 ? ( + + ) : error ? ( +
    +
    +

    Error

    +

    {JSON.stringify(error)}

    + + +
    +
    + ) : ( + + )} +
    +
    + ); +} From 345cc4493cdb197a276237b1365afba037d60e75 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 2 Jul 2025 06:47:10 +0000 Subject: [PATCH 3/5] Fix: Resolve HistoryPanel TypeError and /sessions 404 - Added GET and POST /sessions endpoints to backend/src/agent/app.py using an in-memory list for session storage. - Modified frontend/src/App.tsx to improve error handling when fetching session history, ensuring the history state defaults to an empty array to prevent '.map is not a function' errors. --- backend/src/agent/app.py | 22 +++++++++++++++++++++- frontend/src/App.tsx | 13 ++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/backend/src/agent/app.py b/backend/src/agent/app.py index f20f6ed3..6de82dc3 100644 --- a/backend/src/agent/app.py +++ b/backend/src/agent/app.py @@ -1,11 +1,31 @@ # mypy: disable - error - code = "no-untyped-def,misc" import pathlib -from fastapi import FastAPI, Response +from typing import List, Any +from fastapi import FastAPI, Response, Body from fastapi.staticfiles import StaticFiles +from pydantic import BaseModel # Define the FastAPI app app = FastAPI() +# In-memory store for sessions +sessions_db: List[Any] = [] + + +class SessionItem(BaseModel): + session_id: str + messages: List[Any] # Replace Any with your Message model if available + historicalActivities: Any # Replace Any with your Activity model if available + +@app.post("/sessions") +async def save_session(session_item: SessionItem = Body(...)): + sessions_db.append(session_item.dict()) + return {"status": "ok", "session_id": session_item.session_id} + +@app.get("/sessions") +async def get_sessions() -> List[Any]: + return sessions_db + def create_frontend_router(build_dir="../frontend/dist"): """Creates a router to serve the React frontend. diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index bf4e6ebb..28cdecad 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -119,10 +119,21 @@ export default function App() { async function fetchHistory() { try { const response = await fetch(`${API_URL}/sessions`); + if (!response.ok) { + console.error("Failed to fetch history: Network response was not ok", response.statusText); + setHistory([]); // Ensure history is an array even on error + return; + } const data = await response.json(); - setHistory(data); + if (Array.isArray(data)) { + setHistory(data); + } else { + console.error("Failed to fetch history: Data is not an array", data); + setHistory([]); // Ensure history is an array if data format is incorrect + } } catch (error) { console.error("Failed to fetch history:", error); + setHistory([]); // Ensure history is an array on any other error } } fetchHistory(); From cbd06d6ec6149cb9cdacef163ddcb413c16d92eb Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 2 Jul 2025 15:27:21 +0000 Subject: [PATCH 4/5] feat: Build frontend and include dist for backend serving This commit includes the built static assets for the frontend in the `frontend/dist` directory. This is intended to resolve the issue where the backend server could not find the frontend build directory, which was likely causing the UI to not load correctly and messages to not be displayed. From 89d7ba9bf4e0bbdcc747c9a79028362fbc4c9631 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 3 Jul 2025 05:58:45 +0000 Subject: [PATCH 5/5] Add path debugging to backend app.py Includes extensive print statements in `backend/src/agent/app.py` around the `create_frontend_router` function to help diagnose why the frontend build directory is not being found in the user's environment. --- backend/src/agent/app.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/backend/src/agent/app.py b/backend/src/agent/app.py index 6de82dc3..8108794d 100644 --- a/backend/src/agent/app.py +++ b/backend/src/agent/app.py @@ -38,6 +38,32 @@ def create_frontend_router(build_dir="../frontend/dist"): """ build_path = pathlib.Path(__file__).parent.parent.parent / build_dir + print(f"DEBUG: __file__ is {__file__}") + print(f"DEBUG: build_dir is {build_dir}") + print(f"DEBUG: Calculated build_path is {build_path}") + try: + abs_build_path = build_path.resolve() + print(f"DEBUG: Absolute build_path is {abs_build_path}") + print(f"DEBUG: build_path exists: {abs_build_path.exists()}") + print(f"DEBUG: build_path is_dir: {abs_build_path.is_dir()}") + index_html_path = abs_build_path / "index.html" + print(f"DEBUG: index_html_path is {index_html_path}") + print(f"DEBUG: index_html_path exists: {index_html_path.exists()}") + print(f"DEBUG: index_html_path is_file: {index_html_path.is_file()}") + + # Try to list directories - this might fail if paths are incorrect or due to permissions + try: + frontend_dir_to_list = pathlib.Path("C:/AI-Programmi/new/gemini-fullstack-langgraph-quickstart/frontend") + print(f"DEBUG: Listing {frontend_dir_to_list}: {list(frontend_dir_to_list.iterdir()) if frontend_dir_to_list.exists() else 'DOES NOT EXIST'}") + frontend_dist_dir_to_list = pathlib.Path("C:/AI-Programmi/new/gemini-fullstack-langgraph-quickstart/frontend/dist") + print(f"DEBUG: Listing {frontend_dist_dir_to_list}: {list(frontend_dist_dir_to_list.iterdir()) if frontend_dist_dir_to_list.exists() else 'DOES NOT EXIST'}") + except Exception as e: + print(f"DEBUG: Error listing directories: {e}") + + except Exception as e: + print(f"DEBUG: Error resolving or checking build_path: {e}") + + if not build_path.is_dir() or not (build_path / "index.html").is_file(): print( f"WARN: Frontend build directory not found or incomplete at {build_path}. Serving frontend will likely fail."