[PAI-894] fix: Enhance error handling and display in action status block and su… (#4646)

This commit is contained in:
pratapalakshmi
2025-12-02 14:41:13 +05:30
committed by GitHub
parent 5c7eedb4a1
commit 970afdece9
6 changed files with 96 additions and 23 deletions
@@ -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>
+5 -1
View File
@@ -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",
}
);
});
}
+23 -2
View File
@@ -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;
}
+14 -4
View File
@@ -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;