+ {/* Left: entity list grouped by type */}
+
+
+ {/* Create new entity */}
+
+
+
+ {/* Entity list grouped by type */}
+ {ALL_ENTITY_TYPES.map((type) => {
+ const typeEntities = store.entitiesByType[type];
+ if (typeEntities.length === 0) return null;
+ return (
+
+
+ {ENTITY_TYPE_LABELS[type]}
+
+ {typeEntities.map((entity) => (
+
+ {renamingId === entity.id ? (
+ setRenameValue(e.currentTarget.value)}
+ onBlur={handleRename}
+ onKeyDown={(e) => e.key === 'Enter' && handleRename()}
+ autoFocus
+ style={{ flex: 1 }}
+ styles={{ input: { fontSize: '0.8125rem' } }}
+ />
+ ) : (
+ setSelectedId(entity.id)}
+ style={{ flex: 1, borderRadius: 'var(--mantine-radius-sm)' }}
+ rightSection={
+
+ {Object.keys(entity.fields).filter((k) => !k.startsWith('_')).length}
+
+ }
+ />
+ )}
+
+
+ ))}
+
+ );
+ })}
+
+
+
+ {/* Right: fields for selected entity */}
+
+ {!selectedEntity ? (
+
+ Select an entity to view its fields.
+
+ ) : entries.length === 0 ? (
+
+ No fields in this entity yet. Import a document or add fields manually.
+
+ ) : (
+
+
+
+
+ Field
+ Value
+
+
+
+
+ {entries.map(([key, val]) => (
+
+ {key}
+
+ {editingKey === key ? (
+ setEditValue(e.currentTarget.value)}
+ onBlur={() => handleSaveEdit(key)}
+ onKeyDown={(e) => e.key === 'Enter' && handleSaveEdit(key)}
+ autoFocus
+ styles={{ input: { fontSize: '0.8125rem' } }}
+ />
+ ) : (
+ {
+ setEditingKey(key);
+ setEditValue(val);
+ }}
+ >
+ {val}
+
+ )}
+
+
+ store.removeField(selectedEntity.id, key)}
+ >
+
+
+
+
+ ))}
+
+
+
+
+ )}
+
+ {/* Import/Export buttons */}
+
+ {importMessage && (
+ {importMessage}
+ )}
+
+ }
+ onClick={() => {
+ const json = exportEntitiesToJson(store.entities);
+ downloadJson(json, `stirling-entities-${new Date().toISOString().split('T')[0]}.json`);
+ }}
+ >
+ Export JSON
+
+ }
+ onClick={() => {
+ const input = document.createElement('input');
+ input.type = 'file';
+ input.accept = '.json';
+ input.onchange = (e) => {
+ const file = (e.target as HTMLInputElement).files?.[0];
+ if (!file) return;
+ file.text().then((text) => {
+ const result = importEntitiesFromJson(text, store);
+ setImportMessage(
+ result.errors.length > 0
+ ? `Imported ${result.imported}, errors: ${result.errors.join('; ')}`
+ : `Imported ${result.imported} entities.`
+ );
+ });
+ };
+ input.click();
+ }}
+ >
+ Import JSON
+
+ }
+ onClick={() => {
+ const input = document.createElement('input');
+ input.type = 'file';
+ input.accept = '.csv';
+ input.onchange = (e) => {
+ const file = (e.target as HTMLInputElement).files?.[0];
+ if (!file) return;
+ file.text().then((text) => {
+ const result = importCsvToStore(text, newType, store);
+ setImportMessage(
+ result.errors.length > 0
+ ? `Imported ${result.imported}, errors: ${result.errors.join('; ')}`
+ : `Imported ${result.imported} entities as ${ENTITY_TYPE_LABELS[newType]}.`
+ );
+ });
+ };
+ input.click();
+ }}
+ >
+ Import CSV
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/core/tools/aiFormFill/FillPreview.tsx b/frontend/src/core/tools/aiFormFill/FillPreview.tsx
new file mode 100644
index 0000000000..5744cf2289
--- /dev/null
+++ b/frontend/src/core/tools/aiFormFill/FillPreview.tsx
@@ -0,0 +1,112 @@
+/**
+ * Fill Preview — shows proposed field values before applying.
+ * Each field shows: field name, value, source entity, accept/edit/reject toggle.
+ */
+import { useCallback } from 'react';
+import {
+ Stack,
+ Group,
+ Text,
+ Checkbox,
+ TextInput,
+ Badge,
+ Divider,
+} from '@mantine/core';
+import type { PreviewField } from './useBatchFormFillFlow';
+
+function cleanPdfText(text: string): string {
+ let cleaned = text;
+ cleaned = cleaned.replace(/:plus:/g, ' ');
+ cleaned = cleaned.replace(/\+/g, ' ');
+ cleaned = cleaned.replace(/&/g, '&');
+ cleaned = cleaned.replace(/'/g, "'");
+ cleaned = cleaned.replace(/"/g, '"');
+ cleaned = cleaned.replace(/</g, '<');
+ cleaned = cleaned.replace(/>/g, '>');
+ try { cleaned = decodeURIComponent(cleaned); } catch { /* ignore */ }
+ cleaned = cleaned.replace(/\s+/g, ' ').trim();
+ return cleaned;
+}
+
+interface FillPreviewProps {
+ fields: PreviewField[];
+ fileNames: Record