refactor-for-llama-parse (#17)

# MR: LlamaParse pipeline support, fetch request deletion, and UX improvements

**Source branch:** `refactor-for-llama-parse`
**Target branch:** `main`

## Summary

This MR adds support for the `llama_parse` pipeline type across fetch requests, introduces delete functionality for fetch requests (from both the list and detail pages), and navigates to the detail page after a successful create.

## Changes

### Features

- **LlamaParse pipeline support** (`22e6603`)
  - Renamed pipeline type `llama_parser` → `llama_parse` in `fetch-requests.models.ts` to match the backend enum.
  - Updated pipeline progress computation in `PipelineStepper` for the new step naming.

- **Delete fetch requests** (`8694537`)
  - List page: added a delete icon button on each row with confirmation dialog; disabled while the request is processing.
  - Detail page: added a Delete action in the header section with confirmation; redirects back to the list on success.
  - Deletion warns that all associated expenses and ambiguities will be removed.
  - Success/error feedback via toast notifications using `formatApiError`.

- **Navigate after create** (`c6622d4`)
  - After successfully creating a fetch request, the user is now redirected to its detail page instead of staying on the form.

### Refactoring / Cleanup

- **Drop dead llama_extract SSE step** (`c585b62`)
  - Removed unused SSE handling for the obsolete `llama_extract` step.

### Chores

- Added `tsconfig.tsbuildinfo` to `.gitignore` (`1c5d8ed`).

## Files changed

| File | Change |
| --- | --- |
| `.gitignore` | Ignore `tsconfig.tsbuildinfo` |
| `src/FetchRequest/FetchRequestCreate.tsx` | Row-level delete in list; navigate to detail after create |
| `src/FetchRequest/FetchRequestDetail.tsx` | Delete action with confirm + redirect |
| `src/FetchRequest/components/PipelineStepper.tsx` | Progress calc adjustments |
| `src/features/fetch-requests/fetch-requests.models.ts` | `PipelineType`: `llama_parser` → `llama_parse` |

Reviewed-on: #17
Co-authored-by: Vishesh 'ironeagle' Bangotra <aetoskia@gmail.com>
Co-committed-by: Vishesh 'ironeagle' Bangotra <aetoskia@gmail.com>
This commit is contained in:
2026-08-23 16:58:32 +00:00
committed by aetos
parent 2add23efe7
commit 9377460e79
5 changed files with 95 additions and 5 deletions

1
.gitignore vendored
View File

@@ -14,3 +14,4 @@ dist
dist-ssr
*.local
.idea
tsconfig.tsbuildinfo

View File

@@ -1,22 +1,32 @@
import React, { useEffect, useMemo, useState } from "react";
import {
Box, Container, Typography, Paper, Button, Alert,
CircularProgress,
CircularProgress, IconButton, Tooltip,
} from "@mui/material";
import { useNavigate } from "react-router-dom";
import ReceiptLongIcon from "@mui/icons-material/ReceiptLong";
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
import { useAppContext, useResource, ListCellRenderer, FormFieldRenderer, getApi, applyDisplayFormat } from "../../react-openapi";
import type { FieldConfig } from "../../react-openapi";
import { PageHeader } from "../ui/PageHeader";
import { EmptyState } from "../ui/EmptyState";
import { formatApiError } from "../features/fetch-requests";
import { useToast } from "../ui/Toast";
const CREATE_FIELDS = ["account", "bank", "pipeline", "start_date", "end_date", "source"];
function FetchRequestList() {
const navigate = useNavigate();
const { list, resource } = useResource("fetch-requests");
const { list, remove, resource } = useResource("fetch-requests");
const { resources: allResources } = useAppContext();
const [rows, setRows] = useState<any[] | null>(null);
const [deletingId, setDeletingId] = useState<string | null>(null);
const { showToast } = useToast();
const reload = () => {
if (!resource) return;
list({ limit: 20 }).then((res) => setRows(res.items ?? []));
};
const columns = useMemo(() => {
if (!resource) return [];
@@ -30,6 +40,26 @@ function FetchRequestList() {
list({ limit: 20 }).then((res) => setRows(res.items ?? []));
}, [resource?.name]);
const handleDelete = async (e: React.MouseEvent, id: string) => {
e.stopPropagation();
if (
!window.confirm(
"Delete this fetch request along with all its expenses and ambiguities? This cannot be undone.",
)
)
return;
setDeletingId(id);
try {
await remove(id);
showToast("Fetch request deleted");
reload();
} catch (err: any) {
showToast(formatApiError(err), "error");
} finally {
setDeletingId(null);
}
};
if (!rows) {
return (
<Box sx={{ display: "flex", justifyContent: "center", py: 6 }}>
@@ -82,6 +112,18 @@ function FetchRequestList() {
/>
</Box>
))}
<Box sx={{ ml: "auto", flexShrink: 0 }}>
<Tooltip title="Delete fetch request">
<IconButton
size="small"
color="error"
disabled={deletingId === row.id || row.status === "processing"}
onClick={(e) => handleDelete(e, row.id)}
>
<DeleteOutlineIcon fontSize="small" />
</IconButton>
</Tooltip>
</Box>
</Box>
</Paper>
);
@@ -94,6 +136,7 @@ export default function FetchRequestCreate() {
const { resources: allResources } = useAppContext();
const resource = useMemo(() => allResources.find((r) => r.name === "fetch-requests"), [allResources]);
const { create } = useResource("fetch-requests");
const navigate = useNavigate();
const [formData, setFormData] = useState<Record<string, any>>({});
const [fkOptions, setFkOptions] = useState<Record<string, { value: any; label: string }[]>>({});
const [loading, setLoading] = useState(false);
@@ -143,6 +186,10 @@ export default function FetchRequestCreate() {
const display = applyDisplayFormat(created, resource!.displayFormat);
setResult({ severity: "success", message: `Created: ${display}` });
setFormData({});
const newId = (created as any)?.id;
if (newId) {
navigate(`/fetch-requests/${newId}`);
}
} catch (e: any) {
const detail = e?.response?.data?.detail;
const msg = Array.isArray(detail) ? detail.map((d: any) => d.msg).join("; ") : (detail ?? e?.message ?? "Unknown error");

View File

@@ -10,6 +10,7 @@ import {
CircularProgress,
Alert,
Divider,
Tooltip,
} from "@mui/material";
import ReplayIcon from "@mui/icons-material/Replay";
import CheckCircleIcon from "@mui/icons-material/CheckCircle";
@@ -18,6 +19,7 @@ import WarningAmberIcon from "@mui/icons-material/WarningAmber";
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
import RemoveCircleOutlineIcon from "@mui/icons-material/RemoveCircleOutline";
import FiberManualRecordIcon from "@mui/icons-material/FiberManualRecord";
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
import { useResource, useItemSse, useAppContext, DetailFieldRenderer, applyDisplayFormat } from "../../react-openapi";
import { useQuery } from "@tanstack/react-query";
import { RETRY_MAX, formatApiError } from "../features/fetch-requests";
@@ -85,11 +87,12 @@ function Section({ title, children, action }: { title: string; children: React.R
export default function FetchRequestDetail() {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const { get, patch, resource } = useResource("fetch-requests");
const { get, patch, remove, resource } = useResource("fetch-requests");
const { resources: allResources } = useAppContext();
const [stepStats, setStepStats] = useState<Record<string, number>>({});
const [liveParsedCount, setLiveParsedCount] = useState<number | undefined>(undefined);
const [retrying, setRetrying] = useState(false);
const [deleting, setDeleting] = useState(false);
const { showToast } = useToast();
const feedRef = useRef<HTMLDivElement>(null);
@@ -174,6 +177,26 @@ export default function FetchRequestDetail() {
}
};
const handleDelete = async () => {
if (!id || !remove || deleting) return;
if (
!window.confirm(
"Delete this fetch request along with all its expenses and ambiguities? This cannot be undone.",
)
)
return;
setDeleting(true);
try {
await remove(id);
showToast("Fetch request deleted");
navigate("/fetch-requests");
} catch (err: any) {
showToast(formatApiError(err), "error");
} finally {
setDeleting(false);
}
};
const req = fetchRequest as any;
const retryCount = req?.retry_count ?? 0;
const isRetryExhausted = retryCount >= RETRY_MAX;
@@ -286,6 +309,20 @@ export default function FetchRequestDetail() {
Retry
</Button>
)}
<Tooltip title={status === "processing" ? "Cannot delete while processing" : "Delete fetch request"}>
<span>
<Button
variant="outlined"
size="small"
color="error"
startIcon={<DeleteOutlineIcon />}
onClick={handleDelete}
disabled={deleting || status === "processing"}
>
Delete
</Button>
</span>
</Tooltip>
</Box>
</Section>

View File

@@ -29,7 +29,12 @@ function computeProgressPercent(
let pct = 0;
if (seenSteps.has("raw_lines") || seenSteps.has("txn_blocks")) pct += 10;
if (
seenSteps.has("raw_lines") ||
seenSteps.has("txn_blocks")
) {
pct += 10;
}
if (txnBlockCount > 0) {
const current = Math.max(liveCount, stepStats.txn_dicts ?? 0);

View File

@@ -26,7 +26,7 @@ export interface EmailSource {
txn_dicts_count?: number;
}
export type PipelineType = "heuristic" | "llm" | "llama_parser";
export type PipelineType = "heuristic" | "llm" | "llama_parse";
export interface FetchRequestCreate {
source: FileSource | EmailSource;