Spaces:
Sleeping
Sleeping
Commit ·
1007fa0
1
Parent(s): be54038
Add bundled demo samples to PolicyTrace UI
Browse files- sample_data/policytrace_demo_pack/Certificate of Motor Insurance - Demo.pdf +0 -0
- sample_data/policytrace_demo_pack/Policy Booklet - Demo.pdf +0 -0
- sample_data/policytrace_demo_pack/Schedule of Insurance - Demo.pdf +0 -0
- sample_data/policytrace_demo_pack/Statement of Fact - Demo.pdf +0 -0
- src/api.py +99 -0
- ui/src/UploadPage.tsx +94 -10
- ui/src/api.ts +11 -1
- ui/src/assets/ai-toolstack-logo.svg +14 -17
- ui/src/types.ts +15 -0
sample_data/policytrace_demo_pack/Certificate of Motor Insurance - Demo.pdf
ADDED
|
Binary file (3.38 kB). View file
|
|
|
sample_data/policytrace_demo_pack/Policy Booklet - Demo.pdf
ADDED
|
Binary file (3.03 kB). View file
|
|
|
sample_data/policytrace_demo_pack/Schedule of Insurance - Demo.pdf
ADDED
|
Binary file (7.08 kB). View file
|
|
|
sample_data/policytrace_demo_pack/Statement of Fact - Demo.pdf
ADDED
|
Binary file (3.07 kB). View file
|
|
|
src/api.py
CHANGED
|
@@ -4,6 +4,8 @@ api.py — FastAPI server for the UK Motor Insurance Visual Audit Review UI.
|
|
| 4 |
Endpoints
|
| 5 |
─────────
|
| 6 |
GET /api/health
|
|
|
|
|
|
|
| 7 |
POST /api/process — upload PDFs, run pipeline, return session_id
|
| 8 |
GET /api/session/{id} — full GoldenRecordWithProvenance JSON
|
| 9 |
GET /api/pdf/{session_id}/{file} — serve source PDF (path-traversal safe)
|
|
@@ -26,6 +28,7 @@ import uuid
|
|
| 26 |
from datetime import datetime
|
| 27 |
from pathlib import Path
|
| 28 |
from typing import Optional
|
|
|
|
| 29 |
|
| 30 |
# ── Ensure src/ is on sys.path so sibling modules resolve regardless of CWD ─
|
| 31 |
sys.path.insert(0, str(Path(__file__).parent))
|
|
@@ -59,6 +62,7 @@ _DEBUG_DIR = Path(__file__).parent.parent / "output" / "debug"
|
|
| 59 |
_DEBUG_DIR.mkdir(parents=True, exist_ok=True)
|
| 60 |
|
| 61 |
_STATIC_DIR = Path(__file__).parent.parent / "ui" / "dist"
|
|
|
|
| 62 |
|
| 63 |
# ---------------------------------------------------------------------------
|
| 64 |
# App
|
|
@@ -136,6 +140,33 @@ def _count_leaves(obj: object) -> int:
|
|
| 136 |
return 1
|
| 137 |
|
| 138 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
# ---------------------------------------------------------------------------
|
| 140 |
# Endpoints
|
| 141 |
# ---------------------------------------------------------------------------
|
|
@@ -146,6 +177,74 @@ async def health():
|
|
| 146 |
return {"status": "ok", "version": "1.0.0"}
|
| 147 |
|
| 148 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
# ── POST /api/process ────────────────────────────────────────────────────────
|
| 150 |
|
| 151 |
class ProcessResponse(BaseModel):
|
|
|
|
| 4 |
Endpoints
|
| 5 |
─────────
|
| 6 |
GET /api/health
|
| 7 |
+
GET /api/samples - list bundled synthetic demo PDFs
|
| 8 |
+
GET /api/samples/{file} - download a bundled synthetic demo PDF
|
| 9 |
POST /api/process — upload PDFs, run pipeline, return session_id
|
| 10 |
GET /api/session/{id} — full GoldenRecordWithProvenance JSON
|
| 11 |
GET /api/pdf/{session_id}/{file} — serve source PDF (path-traversal safe)
|
|
|
|
| 28 |
from datetime import datetime
|
| 29 |
from pathlib import Path
|
| 30 |
from typing import Optional
|
| 31 |
+
from urllib.parse import quote
|
| 32 |
|
| 33 |
# ── Ensure src/ is on sys.path so sibling modules resolve regardless of CWD ─
|
| 34 |
sys.path.insert(0, str(Path(__file__).parent))
|
|
|
|
| 62 |
_DEBUG_DIR.mkdir(parents=True, exist_ok=True)
|
| 63 |
|
| 64 |
_STATIC_DIR = Path(__file__).parent.parent / "ui" / "dist"
|
| 65 |
+
_SAMPLE_PACK_DIR = Path(__file__).parent.parent / "sample_data" / "policytrace_demo_pack"
|
| 66 |
|
| 67 |
# ---------------------------------------------------------------------------
|
| 68 |
# App
|
|
|
|
| 140 |
return 1
|
| 141 |
|
| 142 |
|
| 143 |
+
def _sample_pdf_paths() -> list[Path]:
|
| 144 |
+
"""Return demo PDFs in manifest order, falling back to alphabetical order."""
|
| 145 |
+
if not _SAMPLE_PACK_DIR.exists():
|
| 146 |
+
return []
|
| 147 |
+
|
| 148 |
+
pdfs_by_name = {
|
| 149 |
+
path.name: path
|
| 150 |
+
for path in sorted(_SAMPLE_PACK_DIR.glob("*.pdf"), key=lambda p: p.name.lower())
|
| 151 |
+
}
|
| 152 |
+
manifest_file = _SAMPLE_PACK_DIR / "manifest.json"
|
| 153 |
+
if not manifest_file.exists():
|
| 154 |
+
return list(pdfs_by_name.values())
|
| 155 |
+
|
| 156 |
+
try:
|
| 157 |
+
manifest = json.loads(manifest_file.read_text(encoding="utf-8"))
|
| 158 |
+
except json.JSONDecodeError:
|
| 159 |
+
return list(pdfs_by_name.values())
|
| 160 |
+
|
| 161 |
+
ordered: list[Path] = []
|
| 162 |
+
for filename in manifest.get("files", []):
|
| 163 |
+
path = pdfs_by_name.pop(Path(filename).name, None)
|
| 164 |
+
if path:
|
| 165 |
+
ordered.append(path)
|
| 166 |
+
ordered.extend(pdfs_by_name.values())
|
| 167 |
+
return ordered
|
| 168 |
+
|
| 169 |
+
|
| 170 |
# ---------------------------------------------------------------------------
|
| 171 |
# Endpoints
|
| 172 |
# ---------------------------------------------------------------------------
|
|
|
|
| 177 |
return {"status": "ok", "version": "1.0.0"}
|
| 178 |
|
| 179 |
|
| 180 |
+
class SampleFileInfo(BaseModel):
|
| 181 |
+
filename: str
|
| 182 |
+
size_bytes: int
|
| 183 |
+
url: str
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
class SamplePackResponse(BaseModel):
|
| 187 |
+
pack_name: str
|
| 188 |
+
description: str
|
| 189 |
+
warning: str
|
| 190 |
+
expected_policy_number: Optional[str] = None
|
| 191 |
+
expected_vrm: Optional[str] = None
|
| 192 |
+
files: list[SampleFileInfo]
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
@app.get("/api/samples", response_model=SamplePackResponse)
|
| 196 |
+
async def list_samples():
|
| 197 |
+
"""Return the bundled synthetic PDFs that are safe for public demos."""
|
| 198 |
+
manifest: dict = {}
|
| 199 |
+
manifest_file = _SAMPLE_PACK_DIR / "manifest.json"
|
| 200 |
+
if manifest_file.exists():
|
| 201 |
+
try:
|
| 202 |
+
manifest = json.loads(manifest_file.read_text(encoding="utf-8"))
|
| 203 |
+
except json.JSONDecodeError:
|
| 204 |
+
logger.warning("Could not parse sample manifest at %s", manifest_file)
|
| 205 |
+
|
| 206 |
+
files = [
|
| 207 |
+
SampleFileInfo(
|
| 208 |
+
filename=path.name,
|
| 209 |
+
size_bytes=path.stat().st_size,
|
| 210 |
+
url=f"/api/samples/{quote(path.name)}",
|
| 211 |
+
)
|
| 212 |
+
for path in _sample_pdf_paths()
|
| 213 |
+
]
|
| 214 |
+
return SamplePackResponse(
|
| 215 |
+
pack_name="PolicyTrace demo pack",
|
| 216 |
+
description=manifest.get(
|
| 217 |
+
"purpose",
|
| 218 |
+
"Synthetic demo data for AI Tool Stack PolicyTrace.",
|
| 219 |
+
),
|
| 220 |
+
warning=manifest.get(
|
| 221 |
+
"warning",
|
| 222 |
+
"No real customer, insurer, vehicle, or policy data is included.",
|
| 223 |
+
),
|
| 224 |
+
expected_policy_number=manifest.get("expected_policy_number"),
|
| 225 |
+
expected_vrm=manifest.get("expected_vrm"),
|
| 226 |
+
files=files,
|
| 227 |
+
)
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
@app.get("/api/samples/{filename}")
|
| 231 |
+
async def serve_sample_pdf(filename: str):
|
| 232 |
+
"""Serve a bundled synthetic PDF from the public demo pack."""
|
| 233 |
+
safe_name = Path(filename).name
|
| 234 |
+
if not safe_name.lower().endswith(".pdf"):
|
| 235 |
+
raise HTTPException(status_code=400, detail="Only PDF files can be served.")
|
| 236 |
+
|
| 237 |
+
pdf_path = _SAMPLE_PACK_DIR / safe_name
|
| 238 |
+
if not pdf_path.exists() or not pdf_path.is_file():
|
| 239 |
+
raise HTTPException(status_code=404, detail=f"Sample PDF '{safe_name}' not found.")
|
| 240 |
+
|
| 241 |
+
return FileResponse(
|
| 242 |
+
str(pdf_path),
|
| 243 |
+
media_type="application/pdf",
|
| 244 |
+
headers={"Content-Disposition": f'inline; filename="{safe_name}"'},
|
| 245 |
+
)
|
| 246 |
+
|
| 247 |
+
|
| 248 |
# ── POST /api/process ────────────────────────────────────────────────────────
|
| 249 |
|
| 250 |
class ProcessResponse(BaseModel):
|
ui/src/UploadPage.tsx
CHANGED
|
@@ -1,13 +1,15 @@
|
|
| 1 |
-
import { useCallback, useState } from 'react'
|
| 2 |
import { useNavigate } from 'react-router-dom'
|
| 3 |
import { api } from './api'
|
| 4 |
import { useStore } from './store'
|
|
|
|
| 5 |
import logoUrl from './assets/ai-toolstack-logo.svg'
|
| 6 |
|
| 7 |
const BRAND = {
|
| 8 |
-
dark: '#
|
| 9 |
-
blue: '#
|
| 10 |
-
teal: '#
|
|
|
|
| 11 |
} as const
|
| 12 |
|
| 13 |
export function UploadPage() {
|
|
@@ -18,19 +20,56 @@ export function UploadPage() {
|
|
| 18 |
const [error, setError] = useState<string | null>(null)
|
| 19 |
const [files, setFiles] = useState<File[]>([])
|
| 20 |
const [dragOver, setDragOver] = useState(false)
|
|
|
|
|
|
|
| 21 |
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
setFiles((prev) => {
|
| 26 |
const names = new Set(prev.map((f) => f.name))
|
| 27 |
return [...prev, ...pdfs.filter((f) => !names.has(f.name))]
|
| 28 |
})
|
| 29 |
}, [])
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
const removeFile = (name: string) =>
|
| 32 |
setFiles((prev) => prev.filter((f) => f.name !== name))
|
| 33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
const handleSubmit = async () => {
|
| 35 |
if (!files.length) return
|
| 36 |
setLoading(true)
|
|
@@ -71,9 +110,9 @@ export function UploadPage() {
|
|
| 71 |
<div className="mb-8 text-center">
|
| 72 |
<div className="inline-flex items-center gap-2 mb-4">
|
| 73 |
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" aria-hidden="true">
|
| 74 |
-
<path d="M4 18L14 22L24 18" stroke={BRAND.
|
| 75 |
-
<path d="M4 14L14 18L24 14" stroke={BRAND.
|
| 76 |
-
<path d="M4 10L14 14L24 10L14 6L4 10Z" stroke={BRAND.
|
| 77 |
</svg>
|
| 78 |
<h1 className="text-2xl font-bold tracking-tight" style={{ color: BRAND.dark }}>
|
| 79 |
PolicyTrace
|
|
@@ -128,6 +167,51 @@ export function UploadPage() {
|
|
| 128 |
/>
|
| 129 |
</div>
|
| 130 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
{/* File list */}
|
| 132 |
{files.length > 0 && (
|
| 133 |
<ul className="mt-4 space-y-2">
|
|
|
|
| 1 |
+
import { useCallback, useEffect, useState } from 'react'
|
| 2 |
import { useNavigate } from 'react-router-dom'
|
| 3 |
import { api } from './api'
|
| 4 |
import { useStore } from './store'
|
| 5 |
+
import type { SamplePack } from './types'
|
| 6 |
import logoUrl from './assets/ai-toolstack-logo.svg'
|
| 7 |
|
| 8 |
const BRAND = {
|
| 9 |
+
dark: '#07111D',
|
| 10 |
+
blue: '#5166D6',
|
| 11 |
+
teal: '#4EA8A8',
|
| 12 |
+
mint: '#63E0DC',
|
| 13 |
} as const
|
| 14 |
|
| 15 |
export function UploadPage() {
|
|
|
|
| 20 |
const [error, setError] = useState<string | null>(null)
|
| 21 |
const [files, setFiles] = useState<File[]>([])
|
| 22 |
const [dragOver, setDragOver] = useState(false)
|
| 23 |
+
const [samplePack, setSamplePack] = useState<SamplePack | null>(null)
|
| 24 |
+
const [loadingSamples, setLoadingSamples] = useState(false)
|
| 25 |
|
| 26 |
+
useEffect(() => {
|
| 27 |
+
let isMounted = true
|
| 28 |
+
api.getSamples()
|
| 29 |
+
.then((pack) => {
|
| 30 |
+
if (isMounted) setSamplePack(pack)
|
| 31 |
+
})
|
| 32 |
+
.catch(() => {
|
| 33 |
+
if (isMounted) setSamplePack(null)
|
| 34 |
+
})
|
| 35 |
+
return () => {
|
| 36 |
+
isMounted = false
|
| 37 |
+
}
|
| 38 |
+
}, [])
|
| 39 |
+
|
| 40 |
+
const addPdfFiles = useCallback((incoming: File[]) => {
|
| 41 |
+
const pdfs = incoming.filter((f) => f.name.toLowerCase().endsWith('.pdf'))
|
| 42 |
setFiles((prev) => {
|
| 43 |
const names = new Set(prev.map((f) => f.name))
|
| 44 |
return [...prev, ...pdfs.filter((f) => !names.has(f.name))]
|
| 45 |
})
|
| 46 |
}, [])
|
| 47 |
|
| 48 |
+
const handleFiles = useCallback((incoming: FileList | null) => {
|
| 49 |
+
if (!incoming) return
|
| 50 |
+
addPdfFiles(Array.from(incoming))
|
| 51 |
+
}, [addPdfFiles])
|
| 52 |
+
|
| 53 |
const removeFile = (name: string) =>
|
| 54 |
setFiles((prev) => prev.filter((f) => f.name !== name))
|
| 55 |
|
| 56 |
+
const handleLoadSamples = async () => {
|
| 57 |
+
if (!samplePack?.files.length) return
|
| 58 |
+
setLoadingSamples(true)
|
| 59 |
+
setError(null)
|
| 60 |
+
try {
|
| 61 |
+
const demoFiles = await Promise.all(
|
| 62 |
+
samplePack.files.map((sample) => api.downloadSampleFile(sample)),
|
| 63 |
+
)
|
| 64 |
+
addPdfFiles(demoFiles)
|
| 65 |
+
} catch (err: unknown) {
|
| 66 |
+
const msg = err instanceof Error ? err.message : 'Could not load the demo sample files.'
|
| 67 |
+
setError(msg)
|
| 68 |
+
} finally {
|
| 69 |
+
setLoadingSamples(false)
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
const handleSubmit = async () => {
|
| 74 |
if (!files.length) return
|
| 75 |
setLoading(true)
|
|
|
|
| 110 |
<div className="mb-8 text-center">
|
| 111 |
<div className="inline-flex items-center gap-2 mb-4">
|
| 112 |
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" aria-hidden="true">
|
| 113 |
+
<path d="M4 18L14 22L24 18" stroke={BRAND.blue} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
| 114 |
+
<path d="M4 14L14 18L24 14" stroke={BRAND.teal} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
| 115 |
+
<path d="M4 10L14 14L24 10L14 6L4 10Z" stroke={BRAND.mint} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
| 116 |
</svg>
|
| 117 |
<h1 className="text-2xl font-bold tracking-tight" style={{ color: BRAND.dark }}>
|
| 118 |
PolicyTrace
|
|
|
|
| 167 |
/>
|
| 168 |
</div>
|
| 169 |
|
| 170 |
+
{samplePack?.files.length ? (
|
| 171 |
+
<section className="mt-4 rounded-2xl border border-teal-100 bg-white p-4 shadow-sm">
|
| 172 |
+
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
| 173 |
+
<div>
|
| 174 |
+
<h2 className="text-sm font-semibold" style={{ color: BRAND.dark }}>
|
| 175 |
+
Try the synthetic demo pack
|
| 176 |
+
</h2>
|
| 177 |
+
<p className="mt-1 text-xs leading-relaxed text-gray-500">
|
| 178 |
+
Safe sample PDFs are bundled with the Hugging Face Space. Load them here,
|
| 179 |
+
then run the same upload and review flow.
|
| 180 |
+
</p>
|
| 181 |
+
{(samplePack.expected_policy_number || samplePack.expected_vrm) && (
|
| 182 |
+
<p className="mt-2 text-xs font-medium" style={{ color: BRAND.teal }}>
|
| 183 |
+
Expected: {samplePack.expected_policy_number}
|
| 184 |
+
{samplePack.expected_vrm ? ` / ${samplePack.expected_vrm}` : ''}
|
| 185 |
+
</p>
|
| 186 |
+
)}
|
| 187 |
+
</div>
|
| 188 |
+
<button
|
| 189 |
+
type="button"
|
| 190 |
+
onClick={handleLoadSamples}
|
| 191 |
+
disabled={loadingSamples}
|
| 192 |
+
className="shrink-0 rounded-xl px-4 py-2 text-xs font-semibold text-white transition-colors disabled:opacity-50"
|
| 193 |
+
style={{ backgroundColor: BRAND.teal }}
|
| 194 |
+
>
|
| 195 |
+
{loadingSamples ? 'Loading samples...' : 'Load demo PDFs'}
|
| 196 |
+
</button>
|
| 197 |
+
</div>
|
| 198 |
+
|
| 199 |
+
<div className="mt-3 flex flex-wrap gap-2">
|
| 200 |
+
{samplePack.files.map((sample) => (
|
| 201 |
+
<a
|
| 202 |
+
key={sample.filename}
|
| 203 |
+
href={sample.url}
|
| 204 |
+
target="_blank"
|
| 205 |
+
rel="noopener noreferrer"
|
| 206 |
+
className="inline-block max-w-full truncate rounded-full border border-gray-200 bg-gray-50 px-3 py-1 text-xs font-medium text-gray-600 hover:border-teal-200 hover:text-teal-700"
|
| 207 |
+
>
|
| 208 |
+
{sample.filename}
|
| 209 |
+
</a>
|
| 210 |
+
))}
|
| 211 |
+
</div>
|
| 212 |
+
</section>
|
| 213 |
+
) : null}
|
| 214 |
+
|
| 215 |
{/* File list */}
|
| 216 |
{files.length > 0 && (
|
| 217 |
<ul className="mt-4 space-y-2">
|
ui/src/api.ts
CHANGED
|
@@ -1,9 +1,19 @@
|
|
| 1 |
import axios from 'axios'
|
| 2 |
-
import type { ProcessResponse, ReviewAction, ReviewState, SessionData } from './types'
|
| 3 |
|
| 4 |
const http = axios.create({ baseURL: '/' })
|
| 5 |
|
| 6 |
export const api = {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
async processDocuments(files: File[]): Promise<ProcessResponse> {
|
| 8 |
const form = new FormData()
|
| 9 |
for (const f of files) form.append('files', f)
|
|
|
|
| 1 |
import axios from 'axios'
|
| 2 |
+
import type { ProcessResponse, ReviewAction, ReviewState, SampleFile, SamplePack, SessionData } from './types'
|
| 3 |
|
| 4 |
const http = axios.create({ baseURL: '/' })
|
| 5 |
|
| 6 |
export const api = {
|
| 7 |
+
async getSamples(): Promise<SamplePack> {
|
| 8 |
+
const { data } = await http.get<SamplePack>('/api/samples')
|
| 9 |
+
return data
|
| 10 |
+
},
|
| 11 |
+
|
| 12 |
+
async downloadSampleFile(sample: SampleFile): Promise<File> {
|
| 13 |
+
const { data } = await http.get<Blob>(sample.url, { responseType: 'blob' })
|
| 14 |
+
return new File([data], sample.filename, { type: 'application/pdf' })
|
| 15 |
+
},
|
| 16 |
+
|
| 17 |
async processDocuments(files: File[]): Promise<ProcessResponse> {
|
| 18 |
const form = new FormData()
|
| 19 |
for (const f of files) form.append('files', f)
|
ui/src/assets/ai-toolstack-logo.svg
CHANGED
|
|
|
|
ui/src/types.ts
CHANGED
|
@@ -168,3 +168,18 @@ export interface ProcessResponse {
|
|
| 168 |
fields_extracted: number
|
| 169 |
provenance_coverage: number
|
| 170 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
fields_extracted: number
|
| 169 |
provenance_coverage: number
|
| 170 |
}
|
| 171 |
+
|
| 172 |
+
export interface SampleFile {
|
| 173 |
+
filename: string
|
| 174 |
+
size_bytes: number
|
| 175 |
+
url: string
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
export interface SamplePack {
|
| 179 |
+
pack_name: string
|
| 180 |
+
description: string
|
| 181 |
+
warning: string
|
| 182 |
+
expected_policy_number?: string
|
| 183 |
+
expected_vrm?: string
|
| 184 |
+
files: SampleFile[]
|
| 185 |
+
}
|