Workflow Copilot: more logs and minor UI improvements (#4425)
This commit is contained in:
committed by
GitHub
parent
ce05dd19cf
commit
37f5d6b8b2
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef, memo } from "react";
|
||||||
import { getClient } from "@/api/AxiosClient";
|
import { getClient } from "@/api/AxiosClient";
|
||||||
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||||
import { useIsSkyvernUser } from "@/hooks/useIsSkyvernUser";
|
import { useIsSkyvernUser } from "@/hooks/useIsSkyvernUser";
|
||||||
@@ -16,6 +16,39 @@ interface ChatMessage {
|
|||||||
timestamp?: string;
|
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 (
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div
|
||||||
|
className={`flex h-8 w-8 items-center justify-center rounded-full text-xs font-bold text-white ${
|
||||||
|
message.sender === "ai" ? "bg-blue-600" : "bg-purple-600"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{message.sender === "ai" ? "AI" : "U"}
|
||||||
|
</div>
|
||||||
|
<div className="relative flex-1 rounded-lg bg-slate-800 p-3 pr-12">
|
||||||
|
<p className="text-sm text-slate-200">{message.content}</p>
|
||||||
|
{message.timestamp ? (
|
||||||
|
<span className="pointer-events-none absolute bottom-2 right-2 rounded bg-slate-900/70 px-1.5 py-0.5 text-[10px] text-slate-400">
|
||||||
|
{formatChatTimestamp(message.timestamp)}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
interface WorkflowCopilotChatProps {
|
interface WorkflowCopilotChatProps {
|
||||||
onWorkflowUpdate?: (workflowYaml: string) => void;
|
onWorkflowUpdate?: (workflowYaml: string) => void;
|
||||||
isOpen?: boolean;
|
isOpen?: boolean;
|
||||||
@@ -111,18 +144,6 @@ export function WorkflowCopilotChat({
|
|||||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||||
const { getSaveData } = useWorkflowHasChangesStore();
|
const { getSaveData } = useWorkflowHasChangesStore();
|
||||||
const hasInitializedPosition = useRef(false);
|
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 hasScrolledOnLoad = useRef(false);
|
||||||
|
|
||||||
const scrollToBottom = (behavior: ScrollBehavior) => {
|
const scrollToBottom = (behavior: ScrollBehavior) => {
|
||||||
@@ -296,13 +317,19 @@ export function WorkflowCopilotChat({
|
|||||||
updated_workflow_yaml: string | null;
|
updated_workflow_yaml: string | null;
|
||||||
request_time: string;
|
request_time: string;
|
||||||
response_time: string;
|
response_time: string;
|
||||||
}>("/workflow/copilot/chat-post", {
|
}>(
|
||||||
workflow_permanent_id: workflowPermanentId,
|
"/workflow/copilot/chat-post",
|
||||||
workflow_copilot_chat_id: workflowCopilotChatId,
|
{
|
||||||
workflow_run_id: workflowRunId,
|
workflow_permanent_id: workflowPermanentId,
|
||||||
message: messageContent,
|
workflow_copilot_chat_id: workflowCopilotChatId,
|
||||||
workflow_yaml: workflowYaml,
|
workflow_run_id: workflowRunId,
|
||||||
});
|
message: messageContent,
|
||||||
|
workflow_yaml: workflowYaml,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timeout: 300000,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
setWorkflowCopilotChatId(response.data.workflow_copilot_chat_id);
|
setWorkflowCopilotChatId(response.data.workflow_copilot_chat_id);
|
||||||
|
|
||||||
@@ -343,10 +370,6 @@ export function WorkflowCopilotChat({
|
|||||||
id: Date.now().toString(),
|
id: Date.now().toString(),
|
||||||
sender: "ai",
|
sender: "ai",
|
||||||
content: "Sorry, I encountered an error. Please try again.",
|
content: "Sorry, I encountered an error. Please try again.",
|
||||||
timestamp: new Date().toLocaleTimeString("en-US", {
|
|
||||||
hour: "numeric",
|
|
||||||
minute: "2-digit",
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
setMessages((prev) => [...prev, errorMessage]);
|
setMessages((prev) => [...prev, errorMessage]);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -563,29 +586,13 @@ export function WorkflowCopilotChat({
|
|||||||
the target site, and any credentials it should use.
|
the target site, and any credentials it should use.
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-2 text-slate-400">
|
<p className="mt-2 text-slate-400">
|
||||||
Example: “Build a workflow visit hackernews and get top 3 news
|
Example: "Build workflow to find the top post on hackernews
|
||||||
items”
|
today"
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{messages.map((message) => (
|
{messages.map((message) => (
|
||||||
<div key={message.id} className="flex items-start gap-3">
|
<MessageItem key={message.id} message={message} />
|
||||||
<div
|
|
||||||
className={`flex h-8 w-8 items-center justify-center rounded-full text-xs font-bold text-white ${
|
|
||||||
message.sender === "ai" ? "bg-blue-600" : "bg-purple-600"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{message.sender === "ai" ? "AI" : "U"}
|
|
||||||
</div>
|
|
||||||
<div className="relative flex-1 rounded-lg bg-slate-800 p-3 pr-12">
|
|
||||||
<p className="text-sm text-slate-200">{message.content}</p>
|
|
||||||
{message.timestamp ? (
|
|
||||||
<span className="pointer-events-none absolute bottom-2 right-2 rounded bg-slate-900/70 px-1.5 py-0.5 text-[10px] text-slate-400">
|
|
||||||
{formatChatTimestamp(message.timestamp)}
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
|
|||||||
@@ -1729,7 +1729,8 @@ function Workspace({
|
|||||||
|
|
||||||
workflowChangesStore.setHasChanges(true);
|
workflowChangesStore.setHasChanges(true);
|
||||||
} catch (error) {
|
} 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({
|
toast({
|
||||||
title: "Update failed",
|
title: "Update failed",
|
||||||
description: "Failed to parse workflow YAML. Please try again.",
|
description: "Failed to parse workflow YAML. Please try again.",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import time
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -103,14 +104,35 @@ async def copilot_call_llm(
|
|||||||
)
|
)
|
||||||
|
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Calling LLM for workflow copilot",
|
"Calling LLM",
|
||||||
prompt_length=len(llm_prompt),
|
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(
|
llm_response = await app.LLM_API_HANDLER(
|
||||||
prompt=llm_prompt,
|
prompt=llm_prompt,
|
||||||
prompt_name="workflow-copilot",
|
prompt_name="workflow-copilot",
|
||||||
organization_id=organization_id,
|
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:
|
if isinstance(llm_response, dict) and "output" in llm_response:
|
||||||
action_data = llm_response["output"]
|
action_data = llm_response["output"]
|
||||||
|
|||||||
Reference in New Issue
Block a user