diff --git a/src/FetchRequestDetail.tsx b/src/FetchRequestDetail.tsx
index dac90e7..560013b 100644
--- a/src/FetchRequestDetail.tsx
+++ b/src/FetchRequestDetail.tsx
@@ -15,6 +15,7 @@ import {
StepIcon,
LinearProgress,
IconButton,
+ Snackbar,
} from "@mui/material";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import ReplayIcon from "@mui/icons-material/Replay";
@@ -114,6 +115,7 @@ function formatProgressMessage(msg: ProgressMessage): string {
if (msg.count !== undefined && msg.unit) return `${msg.count} ${msg.unit}`;
if (msg.count !== undefined) return `${msg.count} items`;
if (msg.raw_ocr_line) return `"${msg.raw_ocr_line.slice(0, 60)}${msg.raw_ocr_line.length > 60 ? "…" : ""}"`;
+ if (msg.error) return msg.error.slice(0, 80);
return "";
}
@@ -121,6 +123,7 @@ function sseIcon(status: SSEEvent["status"]) {
switch (status) {
case "started": return ;
case "completed": return ;
+ case "failed": return ;
case "skipped": return ;
case "paused": return ;
case "progress": return (
@@ -154,6 +157,7 @@ export default function FetchRequestDetail() {
const [sseConnected, setSseConnected] = React.useState(false);
const [liveParsedCount, setLiveParsedCount] = React.useState(undefined);
const [stepStats, setStepStats] = React.useState>({});
+ const [failNotif, setFailNotif] = React.useState(null);
const sseRef = React.useRef(null);
const feedRef = React.useRef(null);
@@ -224,6 +228,10 @@ export default function FetchRequestDetail() {
refetchRequest();
refetchAmbiguities();
}
+ if (parsed.status === "failed") {
+ setFailNotif(parsed.message.error || "Fetch request failed");
+ refetchRequest();
+ }
if (parsed.status === "completed" || parsed.step === "resume_extract") {
refetchRequest();
}
@@ -254,7 +262,7 @@ export default function FetchRequestDetail() {
}
}
- const terminalStatuses = new Set(["completed", "skipped", "paused"]);
+ const terminalStatuses = new Set(["completed", "skipped", "paused", "failed"]);
return sseEvents.filter((e, i) => {
if (progressSteps.has(e.step) && e.status === "progress") return i === lastProgressIdx[e.step];
if (e.status === "started") {
@@ -271,6 +279,7 @@ export default function FetchRequestDetail() {
for (const evt of sseEvents) {
steps.add(evt.step);
if (evt.status === "completed") steps.add(`${evt.step}/completed`);
+ if (evt.status === "failed") steps.add(`${evt.step}/failed`);
if (evt.status === "started") steps.add(`${evt.step}/started`);
if (evt.status === "progress") steps.add(`${evt.step}/progress`);
}
@@ -651,6 +660,16 @@ export default function FetchRequestDetail() {
)}
+ setFailNotif(null)}
+ anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
+ >
+ setFailNotif(null)} sx={{ borderRadius: 2 }}>
+ {failNotif}
+
+
);
}
diff --git a/src/features/fetch-requests/fetch-requests.models.ts b/src/features/fetch-requests/fetch-requests.models.ts
index 110f13a..1265b4a 100644
--- a/src/features/fetch-requests/fetch-requests.models.ts
+++ b/src/features/fetch-requests/fetch-requests.models.ts
@@ -87,10 +87,10 @@ export interface ResolveAmbiguityPayload {
export type SSEEventStep =
| "load_content" | "raw_lines" | "txn_blocks" | "txn_dicts"
| "resume_extract" | "extract" | "paused" | "complete" | "enrich"
- | "save_expenses";
+ | "save_expenses" | "pipeline";
export type SSEEventStatus =
- | "started" | "completed" | "skipped" | "paused" | "progress";
+ | "started" | "completed" | "skipped" | "paused" | "progress" | "failed";
export interface ProgressMessage {
lines?: number;
@@ -98,6 +98,7 @@ export interface ProgressMessage {
count?: number;
unit?: string;
raw_ocr_line?: string;
+ error?: string;
}
export interface SSEEvent {