[PAI-894] fix: Enhance error handling and display in action status block and su… (#4646)
This commit is contained in:
@@ -325,7 +325,23 @@ async def extract_execution_status_from_flow_steps(
|
||||
if is_executed and is_successful:
|
||||
action_data["message"] = "Action completed successfully"
|
||||
elif is_executed and not is_successful:
|
||||
action_data["error"] = "Action failed"
|
||||
# Prefer the detailed execution_error from the flow step, then fall back to execution_result
|
||||
error_message = None
|
||||
try:
|
||||
if getattr(step, "execution_error", None):
|
||||
error_message = str(step.execution_error)
|
||||
elif step.execution_data and isinstance(step.execution_data, dict):
|
||||
exec_result = step.execution_data.get("execution_result")
|
||||
if exec_result:
|
||||
error_message = str(exec_result)
|
||||
except Exception:
|
||||
error_message = None
|
||||
|
||||
# Truncate very long errors to keep the response compact
|
||||
if error_message:
|
||||
action_data["error"] = f"{error_message[:200]}..." if len(error_message) > 200 else error_message
|
||||
else:
|
||||
action_data["error"] = None
|
||||
|
||||
actions.append(action_data)
|
||||
|
||||
|
||||
@@ -21,8 +21,9 @@ type TProps = {
|
||||
|
||||
const ActionStatusBlock = (props: TProps) => {
|
||||
// props
|
||||
|
||||
const { isLatest, isPiThinking, workspaceSlug, workspaceId, query_id, activeChatId, isPiTyping, dialogue } = props;
|
||||
const { execution_status, action_summary, actions } = dialogue;
|
||||
const { execution_status, action_summary, actions, action_error } = dialogue;
|
||||
// states
|
||||
const [isExecutingAction, setIsExecutingAction] = useState(false);
|
||||
// store
|
||||
@@ -43,7 +44,7 @@ const ActionStatusBlock = (props: TProps) => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Action failed!",
|
||||
message: e?.detail,
|
||||
message: e?.error ?? e?.detail ?? e?.message ?? "Unable to execute action.",
|
||||
});
|
||||
} finally {
|
||||
setIsExecutingAction(false);
|
||||
@@ -78,15 +79,24 @@ const ActionStatusBlock = (props: TProps) => {
|
||||
</div>
|
||||
);
|
||||
// Render summary if execution status is executing or action summary is present
|
||||
const shouldShowSummary = execution_status === EExecutionStatus.EXECUTING || Boolean(action_summary);
|
||||
|
||||
if (!shouldShowSummary && !action_error) return null;
|
||||
|
||||
return (
|
||||
(execution_status === EExecutionStatus.EXECUTING || action_summary) && (
|
||||
<SummaryBlock
|
||||
summary={dialogue.action_summary}
|
||||
chatId={activeChatId}
|
||||
status={execution_status}
|
||||
query_id={query_id}
|
||||
/>
|
||||
)
|
||||
<div className="flex flex-col gap-2">
|
||||
{action_error && (
|
||||
<div className="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">{action_error}</div>
|
||||
)}
|
||||
{shouldShowSummary && (
|
||||
<SummaryBlock
|
||||
summary={dialogue.action_summary}
|
||||
chatId={activeChatId}
|
||||
status={execution_status}
|
||||
query_id={query_id}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -109,11 +109,23 @@ export const SummaryBlock = observer((props: TProps) => {
|
||||
</div>
|
||||
<div className="flex flex-col gap-3 text-xs text-custom-text-300">
|
||||
{groupedArtifacts.failed.map((artifact) => (
|
||||
<div className="flex items-center gap-2" key={artifact.artifact_id}>
|
||||
<div>{getIcon(artifact.artifact_type, "", "text")}</div>
|
||||
<div className="text-sm font-medium line-clamp-2 text-start text-custom-text-300">
|
||||
{artifact.entity_name || "Unknown"}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2" key={artifact.artifact_id}>
|
||||
{(artifact.entity_name || artifact.action) && (
|
||||
<div className="flex items-center gap-2">
|
||||
<div>{getIcon(artifact.artifact_type, "", "text")}</div>
|
||||
<div className="text-sm font-medium line-clamp-2 text-start text-custom-text-300">
|
||||
{artifact.entity_name || artifact.action}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{artifact.error && (
|
||||
<div className="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-600 whitespace-pre-line">
|
||||
{artifact.error}
|
||||
</div>
|
||||
)}
|
||||
{!artifact.error && artifact.message && (
|
||||
<div className="text-xs text-custom-text-300 whitespace-pre-line">{artifact.message}</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -282,7 +282,11 @@ export class PiChatService extends APIService {
|
||||
return this.post(`/api/v1/chat/execute-action/`, data)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
throw (
|
||||
error?.response?.data ?? {
|
||||
error: error?.message ?? "Unable to execute action",
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -256,10 +256,25 @@ export class PiChatStore implements IPiChatStore {
|
||||
dialogue.actions.forEach((action) => {
|
||||
const artifact = this.artifactsStore.getArtifact(action.artifact_id);
|
||||
if (!artifact) return;
|
||||
|
||||
// Pull in error/message/entity info from the action as a backup.
|
||||
// This way, users still see why an action failed even after a page refresh.
|
||||
const mergedArtifact: TArtifact = {
|
||||
...artifact,
|
||||
action: artifact.action || action.action,
|
||||
artifact_type: artifact.artifact_type || action.artifact_type,
|
||||
entity_name: artifact.entity_name || action.entity?.entity_name,
|
||||
entity_url: artifact.entity_url || action.entity?.entity_url,
|
||||
// If we have live artifact error/message, use that; otherwise, fall back
|
||||
// to the error/message that came back with the chat history.
|
||||
error: artifact.error ?? action.error,
|
||||
message: artifact.message ?? action.message,
|
||||
};
|
||||
|
||||
if (action.success) {
|
||||
response.successful.push(artifact);
|
||||
response.successful.push(mergedArtifact);
|
||||
} else {
|
||||
response.failed.push(artifact);
|
||||
response.failed.push(mergedArtifact);
|
||||
}
|
||||
});
|
||||
return response;
|
||||
@@ -913,6 +928,8 @@ export class PiChatStore implements IPiChatStore {
|
||||
issue_identifier: action.entity?.issue_identifier,
|
||||
is_executed: true,
|
||||
success: action.success,
|
||||
error: action.error,
|
||||
message: action.message,
|
||||
});
|
||||
if (action.success) {
|
||||
actionableEntities.push(action.artifact_type);
|
||||
@@ -920,6 +937,7 @@ export class PiChatStore implements IPiChatStore {
|
||||
});
|
||||
// update dialogue
|
||||
dialogue.execution_status = EExecutionStatus.COMPLETED;
|
||||
dialogue.action_error = undefined;
|
||||
dialogue.action_summary = response.action_summary;
|
||||
dialogue.actions = response.actions;
|
||||
this.updateDialogue(chatId, actionId, dialogue);
|
||||
@@ -928,6 +946,9 @@ export class PiChatStore implements IPiChatStore {
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dialogue.execution_status = EExecutionStatus.COMPLETED;
|
||||
dialogue.action_error =
|
||||
(error as any)?.error ?? (error as any)?.detail ?? (error as any)?.message ?? "Unable to execute action.";
|
||||
dialogue.action_summary = undefined;
|
||||
this.updateDialogue(chatId, actionId, dialogue);
|
||||
throw error;
|
||||
}
|
||||
|
||||
Vendored
+14
-4
@@ -87,6 +87,8 @@ export type TArtifact = {
|
||||
is_editable: boolean;
|
||||
is_executed: boolean;
|
||||
success: boolean;
|
||||
error?: string;
|
||||
message?: string;
|
||||
artifact_type: string;
|
||||
entity_id?: string;
|
||||
entity_url?: string;
|
||||
@@ -120,6 +122,15 @@ export type TArtifact = {
|
||||
success?: boolean;
|
||||
};
|
||||
|
||||
export type TArtifactWithEntity = TArtifact & {
|
||||
entity?: {
|
||||
entity_id: string;
|
||||
entity_url: string;
|
||||
issue_identifier: string;
|
||||
entity_name: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type TDialogue = {
|
||||
query_id?: string;
|
||||
answer_id?: string;
|
||||
@@ -130,7 +141,7 @@ export type TDialogue = {
|
||||
reasoning?: string;
|
||||
isPiThinking: boolean;
|
||||
execution_status?: EExecutionStatus;
|
||||
actions?: TArtifact[];
|
||||
actions?: TArtifactWithEntity[];
|
||||
action_summary?: {
|
||||
completed: number;
|
||||
duration_seconds: number;
|
||||
@@ -138,6 +149,7 @@ export type TDialogue = {
|
||||
total_planned: number;
|
||||
};
|
||||
attachment_ids?: string[];
|
||||
action_error?: string;
|
||||
};
|
||||
|
||||
export type TChatHistory = {
|
||||
@@ -167,9 +179,7 @@ export type TAction = {
|
||||
export type TExecuteActionResponse = {
|
||||
status: string;
|
||||
message: string;
|
||||
actions: Array<
|
||||
TArtifact & { entity?: { entity_id: string; entity_url: string; issue_identifier: string; entity_name: string } }
|
||||
>;
|
||||
actions: TArtifactWithEntity[];
|
||||
action_summary?: {
|
||||
completed: number;
|
||||
duration_seconds: number;
|
||||
|
||||
Reference in New Issue
Block a user