diff --git a/skyvern-frontend/src/routes/workflows/editor/WorkflowCopilotChat.tsx b/skyvern-frontend/src/routes/workflows/editor/WorkflowCopilotChat.tsx index d8e23dfe..ecf22134 100644 --- a/skyvern-frontend/src/routes/workflows/editor/WorkflowCopilotChat.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/WorkflowCopilotChat.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef } from "react"; +import { useState, useEffect, useRef, memo } from "react"; import { getClient } from "@/api/AxiosClient"; import { useCredentialGetter } from "@/hooks/useCredentialGetter"; import { useIsSkyvernUser } from "@/hooks/useIsSkyvernUser"; @@ -16,6 +16,39 @@ interface ChatMessage { timestamp?: string; } +const formatChatTimestamp = (value: string) => { + let normalizedValue = value.replace(/\.(\d{3})\d*/, ".$1"); + if (!normalizedValue.endsWith("Z")) { + normalizedValue += "Z"; + } + return new Date(normalizedValue).toLocaleTimeString("en-US", { + hour: "numeric", + minute: "2-digit", + }); +}; + +const MessageItem = memo(({ message }: { message: ChatMessage }) => { + return ( +
+
+ {message.sender === "ai" ? "AI" : "U"} +
+
+

{message.content}

+ {message.timestamp ? ( + + {formatChatTimestamp(message.timestamp)} + + ) : null} +
+
+ ); +}); + interface WorkflowCopilotChatProps { onWorkflowUpdate?: (workflowYaml: string) => void; isOpen?: boolean; @@ -111,18 +144,6 @@ export function WorkflowCopilotChat({ const messagesEndRef = useRef(null); const { getSaveData } = useWorkflowHasChangesStore(); const hasInitializedPosition = useRef(false); - - const formatChatTimestamp = (value: string) => { - let normalizedValue = value.replace(/\.(\d{3})\d*/, ".$1"); - if (!normalizedValue.endsWith("Z")) { - normalizedValue += "Z"; - } - return new Date(normalizedValue).toLocaleTimeString("en-US", { - hour: "numeric", - minute: "2-digit", - }); - }; - const hasScrolledOnLoad = useRef(false); const scrollToBottom = (behavior: ScrollBehavior) => { @@ -296,13 +317,19 @@ export function WorkflowCopilotChat({ updated_workflow_yaml: string | null; request_time: string; response_time: string; - }>("/workflow/copilot/chat-post", { - workflow_permanent_id: workflowPermanentId, - workflow_copilot_chat_id: workflowCopilotChatId, - workflow_run_id: workflowRunId, - message: messageContent, - workflow_yaml: workflowYaml, - }); + }>( + "/workflow/copilot/chat-post", + { + workflow_permanent_id: workflowPermanentId, + workflow_copilot_chat_id: workflowCopilotChatId, + workflow_run_id: workflowRunId, + message: messageContent, + workflow_yaml: workflowYaml, + }, + { + timeout: 300000, + }, + ); setWorkflowCopilotChatId(response.data.workflow_copilot_chat_id); @@ -343,10 +370,6 @@ export function WorkflowCopilotChat({ id: Date.now().toString(), sender: "ai", content: "Sorry, I encountered an error. Please try again.", - timestamp: new Date().toLocaleTimeString("en-US", { - hour: "numeric", - minute: "2-digit", - }), }; setMessages((prev) => [...prev, errorMessage]); } finally { @@ -563,29 +586,13 @@ export function WorkflowCopilotChat({ the target site, and any credentials it should use.

- Example: “Build a workflow visit hackernews and get top 3 news - items” + Example: "Build workflow to find the top post on hackernews + today"

) : null} {messages.map((message) => ( -
-
- {message.sender === "ai" ? "AI" : "U"} -
-
-

{message.content}

- {message.timestamp ? ( - - {formatChatTimestamp(message.timestamp)} - - ) : null} -
-
+ ))} {isLoading && (
diff --git a/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx b/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx index c9362809..6f361410 100644 --- a/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx @@ -1729,7 +1729,8 @@ function Workspace({ workflowChangesStore.setHasChanges(true); } catch (error) { - console.error("Failed to parse and apply workflow YAML:", error); + console.error("Failed to parse and apply workflow YAML", error); + console.log("YAML:", workflowYaml); toast({ title: "Update failed", description: "Failed to parse workflow YAML. Please try again.", diff --git a/skyvern/forge/sdk/routes/workflow_copilot.py b/skyvern/forge/sdk/routes/workflow_copilot.py index 0bc7f0a4..ee792068 100644 --- a/skyvern/forge/sdk/routes/workflow_copilot.py +++ b/skyvern/forge/sdk/routes/workflow_copilot.py @@ -1,3 +1,4 @@ +import time from dataclasses import dataclass from datetime import datetime, timezone from pathlib import Path @@ -103,14 +104,35 @@ async def copilot_call_llm( ) LOG.info( - "Calling LLM for workflow copilot", - prompt_length=len(llm_prompt), + "Calling LLM", + user_message=chat_request.message, + user_message_len=len(chat_request.message), + workflow_yaml_len=len(chat_request.workflow_yaml or ""), + chat_history_len=len(chat_history_text), + global_llm_context_len=len(global_llm_context or ""), + debug_run_info_len=len(debug_run_info_text), + workflow_knowledge_base_len=len(workflow_knowledge_base), + llm_prompt_len=len(llm_prompt), + llm_prompt=llm_prompt, ) + llm_start_time = time.monotonic() llm_response = await app.LLM_API_HANDLER( prompt=llm_prompt, prompt_name="workflow-copilot", organization_id=organization_id, ) + LOG.info( + "LLM response", + duration_seconds=time.monotonic() - llm_start_time, + user_message_len=len(chat_request.message), + workflow_yaml_len=len(chat_request.workflow_yaml or ""), + chat_history_len=len(chat_history_text), + global_llm_context_len=len(global_llm_context or ""), + debug_run_info_len=len(debug_run_info_text), + workflow_knowledge_base_len=len(workflow_knowledge_base), + llm_response_len=len(llm_response), + llm_response=llm_response, + ) if isinstance(llm_response, dict) and "output" in llm_response: action_data = llm_response["output"]