From 1ea774df581b0684ba9b6ad4f12fb25d34e70b84 Mon Sep 17 00:00:00 2001 From: Kumbham Ajay Goud Date: Sat, 12 Jul 2025 19:33:29 +0530 Subject: [PATCH 1/2] feat(ui): add ToolResultCard component for displaying tool results --- src/client/components/ToolResultCard.js | 108 ++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 src/client/components/ToolResultCard.js diff --git a/src/client/components/ToolResultCard.js b/src/client/components/ToolResultCard.js new file mode 100644 index 00000000..8b63f325 --- /dev/null +++ b/src/client/components/ToolResultCard.js @@ -0,0 +1,108 @@ +import React, { useState } from "react" +import PropTypes from "prop-types" +import { IconCalendarEvent, IconUser, IconChevronDown, IconChevronUp, IconExternalLink } from "@tabler/icons-react" + +/** + * ToolResultCard Component - Displays a tool result (e.g., event details) in a styled card. + * + * @param {object} props - Component props. + * @param {string} props.title - Main title of the result (e.g., event name). + * @param {string} [props.subtitle] - Subtitle or secondary info (e.g., date/time). + * @param {object|array} [props.details] - Additional details (object or array of key-value pairs). + * @param {array} [props.attendees] - List of attendees (array of strings or objects). + * @param {string} [props.externalLink] - Optional external link for more info. + * @returns {React.ReactNode} + */ +const ToolResultCard = ({ title, subtitle, details, attendees, externalLink }) => { + const [expanded, setExpanded] = useState(false) + + const handleToggle = () => setExpanded((prev) => !prev) + + return ( +
+ {/* Header */} +
+ +

{title}

+ {externalLink && ( + + + Open + + )} +
+ {subtitle &&
{subtitle}
} + {/* Expand/collapse details */} + + {expanded && ( +
+ {/* Render details as key-value pairs if object, or as list if array */} + {details && typeof details === "object" && !Array.isArray(details) && ( +
    + {Object.entries(details).map(([key, value]) => ( +
  • + {key}: {String(value)} +
  • + ))} +
+ )} + {details && Array.isArray(details) && ( +
    + {details.map((item, idx) => ( +
  • {String(item)}
  • + ))} +
+ )} + {/* Attendees */} + {attendees && attendees.length > 0 && ( +
+
+ + Attendees: +
+
    + {attendees.map((att, idx) => ( +
  • + {typeof att === "string" ? att : att.name || JSON.stringify(att)} +
  • + ))} +
+
+ )} +
+ )} +
+ ) +} + +ToolResultCard.propTypes = { + title: PropTypes.string.isRequired, + subtitle: PropTypes.string, + details: PropTypes.oneOfType([ + PropTypes.object, + PropTypes.array + ]), + attendees: PropTypes.arrayOf( + PropTypes.oneOfType([ + PropTypes.string, + PropTypes.object + ]) + ), + externalLink: PropTypes.string +} + +export default ToolResultCard \ No newline at end of file From 40a9b72ebcda9c0710fd252f1461b3d7a139f939 Mon Sep 17 00:00:00 2001 From: Kumbham Ajay Goud Date: Sat, 12 Jul 2025 20:01:42 +0530 Subject: [PATCH 2/2] feat(chat): implement step event logging for backend processes and update UI to display step progress --- src/client/components/ChatOverlay.js | 30 ++++++++++++++++++++++++---- src/server/main/chat/utils.py | 21 +++++++++++++++---- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/client/components/ChatOverlay.js b/src/client/components/ChatOverlay.js index 04d7b9c7..d62d4d9f 100644 --- a/src/client/components/ChatOverlay.js +++ b/src/client/components/ChatOverlay.js @@ -49,6 +49,7 @@ const ChatOverlay = ({ onClose }) => { const [isNewsEnabled, setNewsEnabled] = useState(false) const [isMapsEnabled, setMapsEnabled] = useState(false) const [isShoppingEnabled, setShoppingEnabled] = useState(false) + const [stepEvents, setStepEvents] = useState([]) // NEW: for backend step streaming const textareaRef = useRef(null) const chatEndRef = useRef(null) const abortControllerRef = useRef(null) // To abort fetch requests @@ -86,6 +87,7 @@ const ChatOverlay = ({ onClose }) => { // Add new message and create history for API call const updatedMessages = [...messages, newUserMessage] setMessages(updatedMessages) + setStepEvents([]) // NEW: clear step log for new session setInput("") if (textareaRef.current) textareaRef.current.style.height = "auto" @@ -152,6 +154,10 @@ const ChatOverlay = ({ onClose }) => { toast.error(`An error occurred: ${parsed.message}`) continue } + if (parsed.type === "step") { + setStepEvents((prev) => [...prev, parsed]) + continue + } if (parsed.type === "assistantStream") { const token = parsed.token || parsed.message || "" assistantMessageId = parsed.messageId @@ -163,10 +169,10 @@ const ChatOverlay = ({ onClose }) => { return prev.map((msg, index) => index === existingMsgIndex ? { - ...msg, - content: msg.content + token - } - : msg + ...msg, + content: msg.content + token + } + : msg ) } else { return [ @@ -243,6 +249,22 @@ const ChatOverlay = ({ onClose }) => { + {/* NEW: Step progress log */} + {stepEvents.length > 0 && ( +
+

Backend Process Steps

+
    + {stepEvents.map((step, idx) => ( +
  • + + {step.step} + [{new Date(step.timestamp).toLocaleTimeString()}] +
  • + ))} +
+
+ )} +
AsyncGenerator[Dict[str, Any], None]: assistant_message_id = str(uuid.uuid4()) + def step_event(step, status): + return { + "type": "step", + "step": step, + "status": status, + "timestamp": datetime.datetime.utcnow().isoformat() + "Z" + } + try: + yield step_event("Initializing chat stream", "in_progress") # --- Construct detailed system prompt from user context --- username = user_context.get("name", "User") timezone_str = user_context.get("timezone", "UTC") @@ -50,6 +59,7 @@ async def generate_chat_llm_stream( current_user_time = datetime.datetime.now(user_timezone).strftime('%Y-%m-%d %H:%M:%S %Z') user_profile = await db_manager.get_user_profile(user_id) + yield step_event("Fetched user profile", "done") supermemory_user_id = user_profile.get("userData", {}).get("supermemory_user_id") if user_profile else None active_mcp_servers = {} @@ -60,13 +70,12 @@ async def generate_chat_llm_stream( "transport": "sse", "url": full_supermemory_mcp_url } + yield step_event("Configured MCP servers", "done") active_mcp_servers["chat_tools"] = {"url": INTEGRATIONS_CONFIG["chat_tools"]["mcp_server_config"]["url"], "headers": {"X-User-ID": user_id}} - # ADDED: Journal Server active_mcp_servers["journal_server"] = {"url": INTEGRATIONS_CONFIG["journal"]["mcp_server_config"]["url"], "headers": {"X-User-ID": user_id}} - tool_flags = { "internet_search": enable_internet, "accuweather": enable_weather, @@ -81,8 +90,9 @@ async def generate_chat_llm_stream( if config and "mcp_server_config" in config: mcp_config = config["mcp_server_config"] active_mcp_servers[mcp_config["name"]] = {"url": mcp_config["url"], "headers": {"X-User-ID": user_id}} - + yield step_event("Prepared tool flags and servers", "done") tools = [{"mcpServers": active_mcp_servers}] + yield step_event("System prompt and tools ready", "done") except Exception as e: logger.error(f"Failed during initial setup for chat stream for user {user_id}: {e}", exc_info=True) @@ -94,6 +104,7 @@ async def generate_chat_llm_stream( stream_interrupted = False try: + yield step_event("Starting LLM worker thread", "in_progress") def worker(): try: system_prompt = ( # noqa @@ -103,7 +114,7 @@ def worker(): f"2. **Continuously Learn:** If you learn a new, permanent fact about the user (their preferences, relationships, personal details, goals, etc.), you MUST use the `supermemory-addToSupermemory` tool to remember it for the future. For example, if the user says 'my wife's name is Jane', you must call the tool to save this fact.\n" f"3. **Delegate Complex Tasks:** For requests that require multiple steps, research, or actions over time (e.g., 'plan my trip', 'summarize this topic'), use the `create_task_from_description` tool. Do not try to perform complex tasks yourself.\n" f"4. **Use Your Journal:** For daily notes, simple reminders, or retrieving information from a specific day, use the journal tools (`search_journal`, `summarize_day`, `add_journal_entry`).\n" - f"5. **Final Answer Format:** When you have a complete, final answer for the user that is not a tool call, you MUST wrap it in `` tags. For example: `The weather in London is 15°C and cloudy.`. Any self-correction or thought process should be inside `` tags, which can precede the final answer.\n\n" + f"5. **Final Answer Format:** When you have a complete, final answer for the user that is not a tool call, you MUST wrap it in tags. For example: The weather in London is 15°C and cloudy.. Any self-correction or thought process should be inside tags, which can precede the final answer.\n\n" f"**User Context (for your reference):**\n" f"- **User's Name:** {username}\n" f"- **User's Location:** {location}\n" @@ -124,6 +135,7 @@ def worker(): thread = threading.Thread(target=worker, daemon=True) thread.start() + yield step_event("LLM worker thread started", "done") last_yielded_content_str, final_history_state = "", None while True: @@ -143,6 +155,7 @@ def worker(): except asyncio.CancelledError: stream_interrupted = True; raise finally: + yield step_event("Chat stream complete", "done") yield {"type": "assistantStream", "token": "", "done": True, "messageId": assistant_message_id} await asyncio.to_thread(thread.join)