diff --git a/src/backend/base/langflow/base/agents/events.py b/src/backend/base/langflow/base/agents/events.py
index 4efe01c9d..0c409ab65 100644
--- a/src/backend/base/langflow/base/agents/events.py
+++ b/src/backend/base/langflow/base/agents/events.py
@@ -39,11 +39,17 @@ def _build_agent_input_text_content(agent_input_dict: InputDict) -> str:
def _calculate_duration(start_time: float) -> int:
"""Calculate duration in milliseconds from start time to now."""
+ # Handle the calculation
+ current_time = perf_counter()
if isinstance(start_time, int):
- # means it was transformed into ms so we need to reverse it
- # to whatever perf_counter returns
- return int((perf_counter() - start_time / 1000) * 1000)
- return int((perf_counter() - start_time) * 1000)
+ # If we got an integer, treat it as milliseconds
+ duration = current_time - (start_time / 1000)
+ result = int(duration * 1000)
+ else:
+ # If we got a float, treat it as perf_counter time
+ result = int((current_time - start_time) * 1000)
+
+ return result
def handle_on_chain_start(
@@ -111,6 +117,9 @@ def handle_on_tool_start(
if not agent_message.content_blocks:
agent_message.content_blocks = [ContentBlock(title="Agent Steps", contents=[])]
+ duration = _calculate_duration(start_time)
+ new_start_time = perf_counter() # Get new start time for next operation
+
# Create new tool content with the input exactly as received
tool_content = ToolContent(
type="tool_use",
@@ -119,7 +128,7 @@ def handle_on_tool_start(
output=None,
error=None,
header={"title": f"Accessing **{tool_name}**", "icon": "Hammer"},
- duration=int(start_time * 1000),
+ duration=duration, # Store the actual duration
)
# Store in map and append to message
@@ -128,7 +137,7 @@ def handle_on_tool_start(
agent_message = send_message_method(message=agent_message)
tool_blocks_map[tool_key] = agent_message.content_blocks[0].contents[-1]
- return agent_message, start_time
+ return agent_message, new_start_time
def handle_on_tool_end(
@@ -145,12 +154,13 @@ def handle_on_tool_end(
if tool_content and isinstance(tool_content, ToolContent):
tool_content.output = event["data"].get("output")
- # Calculate duration only when tool ends
+ duration = _calculate_duration(start_time)
+ tool_content.duration = duration
tool_content.header = {"title": f"Executed **{tool_content.name}**", "icon": "Hammer"}
- if isinstance(tool_content.duration, int):
- tool_content.duration = _calculate_duration(tool_content.duration)
+
agent_message = send_message_method(message=agent_message)
- start_time = perf_counter()
+ new_start_time = perf_counter() # Get new start time for next operation
+ return agent_message, new_start_time
return agent_message, start_time
@@ -250,12 +260,11 @@ async def process_agent_events(
agent_message, start_time = tool_handler(
event, agent_message, tool_blocks_map, send_message_method, start_time
)
- start_time = start_time or perf_counter()
elif event["event"] in CHAIN_EVENT_HANDLERS:
chain_handler = CHAIN_EVENT_HANDLERS[event["event"]]
agent_message, start_time = chain_handler(event, agent_message, send_message_method, start_time)
- start_time = start_time or perf_counter()
agent_message.properties.state = "complete"
except Exception as e:
raise ExceptionWithMessageError(agent_message) from e
+
return Message(**agent_message.model_dump())
diff --git a/src/backend/base/langflow/schema/serialize.py b/src/backend/base/langflow/schema/serialize.py
index 345b45914..d441fe686 100644
--- a/src/backend/base/langflow/schema/serialize.py
+++ b/src/backend/base/langflow/schema/serialize.py
@@ -8,7 +8,7 @@ from pydantic.v1 import BaseModel as BaseModelV1
def recursive_serialize_or_str(obj):
try:
- if isinstance(obj, type) and issubclass(obj, BaseModel):
+ if isinstance(obj, type) and issubclass(obj, BaseModel | BaseModelV1):
# This a type BaseModel and not an instance of it
return repr(obj)
if isinstance(obj, str):
diff --git a/src/frontend/src/components/chatComponents/ContentBlockDisplay.tsx b/src/frontend/src/components/chatComponents/ContentBlockDisplay.tsx
index ffd33db45..f9e6fcc20 100644
--- a/src/frontend/src/components/chatComponents/ContentBlockDisplay.tsx
+++ b/src/frontend/src/components/chatComponents/ContentBlockDisplay.tsx
@@ -17,12 +17,14 @@ interface ContentBlockDisplayProps {
contentBlocks: ContentBlock[];
isLoading?: boolean;
state?: string;
+ chatId: string;
}
export function ContentBlockDisplay({
contentBlocks,
isLoading,
state,
+ chatId,
}: ContentBlockDisplayProps) {
const [isExpanded, setIsExpanded] = useState(false);
@@ -100,7 +102,7 @@ export function ContentBlockDisplay({
-
+
)}
-
+
))}
diff --git a/src/frontend/src/components/chatComponents/ContentDisplay.tsx b/src/frontend/src/components/chatComponents/ContentDisplay.tsx
index 99dced81f..fbce64044 100644
--- a/src/frontend/src/components/chatComponents/ContentDisplay.tsx
+++ b/src/frontend/src/components/chatComponents/ContentDisplay.tsx
@@ -8,7 +8,13 @@ import SimplifiedCodeTabComponent from "../codeTabsComponent/ChatCodeTabComponen
import ForwardedIconComponent from "../genericIconComponent";
import DurationDisplay from "./DurationDisplay";
-export default function ContentDisplay({ content }: { content: ContentType }) {
+export default function ContentDisplay({
+ content,
+ chatId,
+}: {
+ content: ContentType;
+ chatId: string;
+}) {
// First render the common BaseContent elements if they exist
const renderHeader = content.header && (
<>
@@ -36,7 +42,7 @@ export default function ContentDisplay({ content }: { content: ContentType }) {
);
const renderDuration = content.duration !== undefined && (
-
+
);
diff --git a/src/frontend/src/components/chatComponents/DurationDisplay.tsx b/src/frontend/src/components/chatComponents/DurationDisplay.tsx
index 46cc8152d..0a408a190 100644
--- a/src/frontend/src/components/chatComponents/DurationDisplay.tsx
+++ b/src/frontend/src/components/chatComponents/DurationDisplay.tsx
@@ -1,34 +1,45 @@
-import { useEffect, useState } from "react";
+import { useDurationStore } from "@/stores/durationStore";
+import { useEffect } from "react";
import { AnimatedNumber } from "../animatedNumbers";
import ForwardedIconComponent from "../genericIconComponent";
import Loading from "../ui/loading";
-export default function DurationDisplay({ duration }: { duration?: number }) {
- const [elapsedTime, setElapsedTime] = useState(0);
- const [intervalId, setIntervalId] = useState(null);
+interface DurationDisplayProps {
+ duration?: number;
+ chatId: string;
+}
+
+export default function DurationDisplay({
+ duration,
+ chatId,
+}: DurationDisplayProps) {
+ const {
+ durations,
+ setDuration,
+ incrementDuration,
+ clearInterval: clearDurationInterval,
+ setInterval: setDurationInterval,
+ } = useDurationStore();
useEffect(() => {
- if (duration !== undefined && intervalId) {
- clearInterval(intervalId);
- setIntervalId(null);
+ if (duration !== undefined) {
+ setDuration(chatId, duration);
+ clearDurationInterval(chatId);
return;
}
- if (duration === undefined && !intervalId) {
- const id = setInterval(() => {
- setElapsedTime((prev) => prev + 10);
- }, 10);
- setIntervalId(id);
- }
+ const intervalId = setInterval(() => {
+ incrementDuration(chatId);
+ }, 10);
+
+ setDurationInterval(chatId, intervalId);
return () => {
- if (intervalId) {
- clearInterval(intervalId);
- }
+ clearDurationInterval(chatId);
};
- }, [duration]);
+ }, [duration, chatId]);
- const displayTime = duration ?? elapsedTime;
+ const displayTime = duration ?? durations[chatId] ?? 0;
const secondsValue = displayTime / 1000;
const humanizedTime = `${secondsValue.toFixed(1)}s`;
diff --git a/src/frontend/src/modals/IOModal/components/chatView/chatMessage/newChatMessage.tsx b/src/frontend/src/modals/IOModal/components/chatView/chatMessage/newChatMessage.tsx
index c29889c9e..5d7e4e2f1 100644
--- a/src/frontend/src/modals/IOModal/components/chatView/chatMessage/newChatMessage.tsx
+++ b/src/frontend/src/modals/IOModal/components/chatView/chatMessage/newChatMessage.tsx
@@ -479,6 +479,7 @@ export default function ChatMessage({
chat.properties?.state === "partial"
}
state={chat.properties?.state}
+ chatId={chat.id}
/>
)}
{!chat.isSend ? (
diff --git a/src/frontend/src/stores/durationStore.ts b/src/frontend/src/stores/durationStore.ts
new file mode 100644
index 000000000..bc2512cd8
--- /dev/null
+++ b/src/frontend/src/stores/durationStore.ts
@@ -0,0 +1,38 @@
+import { create } from "zustand";
+
+interface DurationState {
+ durations: Record;
+ intervals: Record;
+ setDuration: (chatId: string, duration: number) => void;
+ incrementDuration: (chatId: string) => void;
+ clearInterval: (chatId: string) => void;
+ setInterval: (chatId: string, intervalId: NodeJS.Timeout) => void;
+}
+
+export const useDurationStore = create((set) => ({
+ durations: {},
+ intervals: {},
+ setDuration: (chatId, duration) =>
+ set((state) => ({
+ durations: { ...state.durations, [chatId]: duration },
+ })),
+ incrementDuration: (chatId) =>
+ set((state) => ({
+ durations: {
+ ...state.durations,
+ [chatId]: (state.durations[chatId] || 0) + 10,
+ },
+ })),
+ clearInterval: (chatId) =>
+ set((state) => {
+ if (state.intervals[chatId]) {
+ clearInterval(state.intervals[chatId]);
+ }
+ const { [chatId]: _, ...rest } = state.intervals;
+ return { intervals: rest };
+ }),
+ setInterval: (chatId, intervalId) =>
+ set((state) => ({
+ intervals: { ...state.intervals, [chatId]: intervalId },
+ })),
+}));