@deepcitation/deepcitation-js 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -17
- package/lib/parsing/parseCitation.js +13 -4
- package/lib/react/CitationComponent.d.ts +8 -8
- package/lib/react/CitationComponent.js +23 -23
- package/lib/react/CitationVariants.d.ts +3 -3
- package/lib/react/CitationVariants.js +15 -15
- package/lib/react/primitives.d.ts +2 -2
- package/lib/react/primitives.js +4 -4
- package/lib/react/types.d.ts +1 -1
- package/lib/react/utils.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
|
|
19
19
|
LLMs hallucinate. Even when given source documents, they make up quotes, invent statistics, and cite pages that don't exist. DeepCitation solves this by **deterministically verifying every citation** against your source documents—and generating visual proof.
|
|
20
20
|
|
|
21
|
+
<div align="center">
|
|
22
|
+
<img src="./examples/assets/deepcitation-medical-demo.gif" alt="DeepCitation medical documentation demo showing verified inline citations" width="700" />
|
|
23
|
+
<br />
|
|
24
|
+
<em>Medical documentation with verified inline citations — certainty at a glance</em>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
21
27
|
```
|
|
22
28
|
Before: "Revenue grew 45% [1]" → ❓ Did the LLM make this up?
|
|
23
29
|
After: "Revenue grew 45% [1]" → ✅ Verified on page 3, line 12 (with screenshot)
|
|
@@ -59,7 +65,7 @@ import { DeepCitation, wrapCitationPrompt } from "@deepcitation/deepcitation-js"
|
|
|
59
65
|
|
|
60
66
|
const dc = new DeepCitation({ apiKey: process.env.DEEPCITATION_API_KEY });
|
|
61
67
|
|
|
62
|
-
// Upload source files
|
|
68
|
+
// Upload source files, this can be done before the user types their prompt
|
|
63
69
|
const { fileDataParts, deepTextPromptPortion } = await dc.prepareFiles([
|
|
64
70
|
{ file: pdfBuffer, filename: "report.pdf" },
|
|
65
71
|
]);
|
|
@@ -87,31 +93,49 @@ Verify citations against the source documents.
|
|
|
87
93
|
```typescript
|
|
88
94
|
const result = await dc.verifyCitations({
|
|
89
95
|
llmOutput: response.content,
|
|
90
|
-
fileDataParts,
|
|
96
|
+
fileDataParts, //optional
|
|
91
97
|
});
|
|
92
98
|
|
|
93
|
-
// result.
|
|
99
|
+
// result.verifications contains verification status + visual proof
|
|
100
|
+
const { citations, verifications } = result;
|
|
101
|
+
|
|
94
102
|
```
|
|
95
103
|
|
|
96
104
|
### Step 3: Display
|
|
97
105
|
|
|
98
|
-
|
|
106
|
+
Parse the LLM output and render verified citations inline with React components.
|
|
99
107
|
|
|
100
108
|
```tsx
|
|
101
109
|
import { CitationComponent } from "@deepcitation/deepcitation-js/react";
|
|
110
|
+
import {
|
|
111
|
+
parseCitation,
|
|
112
|
+
generateCitationKey,
|
|
113
|
+
} from "@deepcitation/deepcitation-js";
|
|
102
114
|
import "@deepcitation/deepcitation-js/react/styles.css";
|
|
103
115
|
|
|
104
|
-
function Response({
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
116
|
+
function Response({ llmOutput, verifications }) {
|
|
117
|
+
// Split LLM output by citation tags and render inline
|
|
118
|
+
const renderWithCitations = (text: string) => {
|
|
119
|
+
const parts = text.split(/(<cite\s+[^>]*\/>)/g);
|
|
120
|
+
|
|
121
|
+
return parts.map((part, index) => {
|
|
122
|
+
if (part.startsWith("<cite")) {
|
|
123
|
+
const { citation } = parseCitation(part);
|
|
124
|
+
const citationKey = generateCitationKey(citation);
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<CitationComponent
|
|
128
|
+
key={index}
|
|
129
|
+
citation={citation}
|
|
130
|
+
verification={verifications[citationKey]}
|
|
131
|
+
/>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return <span key={index}>{part}</span>;
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return <div>{renderWithCitations(llmOutput)}</div>;
|
|
115
139
|
}
|
|
116
140
|
```
|
|
117
141
|
|
|
@@ -123,8 +147,7 @@ function Response({ citations, verifications }) {
|
|
|
123
147
|
|
|
124
148
|
```typescript
|
|
125
149
|
const dc = new DeepCitation({
|
|
126
|
-
apiKey: string
|
|
127
|
-
apiUrl?: string, // Optional: Custom API URL
|
|
150
|
+
apiKey: string // Your API key (sk-dc-*)
|
|
128
151
|
});
|
|
129
152
|
|
|
130
153
|
// Upload and prepare source files
|
|
@@ -100,8 +100,9 @@ export const parseCitation = (fragment, mdAttachmentId, citationCounterRef, isVe
|
|
|
100
100
|
const citationMatches = [...middleCite.matchAll(citationRegex)];
|
|
101
101
|
const match = citationMatches?.[0];
|
|
102
102
|
const pageNumber = match?.[2] ? parseInt(match?.[2]) : undefined;
|
|
103
|
+
const pageIndex = match?.[3] ? parseInt(match?.[3]) : undefined;
|
|
103
104
|
let fileId = match?.[1];
|
|
104
|
-
let attachmentId = fileId?.length === 20 ? fileId : mdAttachmentId ||
|
|
105
|
+
let attachmentId = fileId?.length === 20 ? fileId : mdAttachmentId || fileId;
|
|
105
106
|
// Use helper to handle escaped quotes inside the phrase
|
|
106
107
|
let fullPhrase = cleanAndUnescape(match?.[4]);
|
|
107
108
|
let keySpan = cleanAndUnescape(match?.[5]);
|
|
@@ -138,8 +139,7 @@ export const parseCitation = (fragment, mdAttachmentId, citationCounterRef, isVe
|
|
|
138
139
|
let timestamps;
|
|
139
140
|
if (avMatch) {
|
|
140
141
|
fileId = avMatch?.[1];
|
|
141
|
-
attachmentId =
|
|
142
|
-
fileId?.length === 20 ? fileId : mdAttachmentId || avMatch?.[1];
|
|
142
|
+
attachmentId = fileId?.length === 20 ? fileId : mdAttachmentId || fileId;
|
|
143
143
|
fullPhrase = cleanAndUnescape(avMatch?.[2]);
|
|
144
144
|
const timestampsString = avMatch?.[3]?.replace(/timestamps=['"]|['"]/g, "");
|
|
145
145
|
const [startTime, endTime] = timestampsString?.split("-") || [];
|
|
@@ -156,6 +156,7 @@ export const parseCitation = (fragment, mdAttachmentId, citationCounterRef, isVe
|
|
|
156
156
|
const citation = {
|
|
157
157
|
fileId: attachmentId,
|
|
158
158
|
pageNumber,
|
|
159
|
+
startPageKey: `page_number_${pageNumber || 1}_index_${pageIndex || 0}`,
|
|
159
160
|
fullPhrase,
|
|
160
161
|
keySpan,
|
|
161
162
|
citationNumber,
|
|
@@ -194,13 +195,21 @@ const parseJsonCitation = (jsonCitation, citationNumber) => {
|
|
|
194
195
|
if (!fullPhrase) {
|
|
195
196
|
return null;
|
|
196
197
|
}
|
|
197
|
-
// Parse startPageKey format: "page_number_PAGE_index_INDEX"
|
|
198
|
+
// Parse startPageKey format: "page_number_PAGE_index_INDEX" or simple "PAGE_INDEX"
|
|
198
199
|
let pageNumber;
|
|
199
200
|
if (startPageKey) {
|
|
201
|
+
// Try full format first: page_number_5_index_2 or pageKey_5_index_2
|
|
200
202
|
const pageMatch = startPageKey.match(/page[_a-zA-Z]*(\d+)_index_(\d+)/i);
|
|
201
203
|
if (pageMatch) {
|
|
202
204
|
pageNumber = parseInt(pageMatch[1], 10);
|
|
203
205
|
}
|
|
206
|
+
else {
|
|
207
|
+
// Try simple n_m format: 5_4 (page 5, index 4)
|
|
208
|
+
const simpleMatch = startPageKey.match(/^(\d+)_(\d+)$/);
|
|
209
|
+
if (simpleMatch) {
|
|
210
|
+
pageNumber = parseInt(simpleMatch[1], 10);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
204
213
|
}
|
|
205
214
|
// Sort lineIds if present
|
|
206
215
|
const lineIds = rawLineIds?.length
|
|
@@ -11,7 +11,7 @@ export type { CitationVariant } from "./types.js";
|
|
|
11
11
|
* ```tsx
|
|
12
12
|
* <CitationComponent
|
|
13
13
|
* citation={{ citationNumber: 1, fullPhrase: "Revenue grew by 25%" }}
|
|
14
|
-
*
|
|
14
|
+
* verification={verificationResult}
|
|
15
15
|
* />
|
|
16
16
|
* // Renders: [1✓] with blue text
|
|
17
17
|
* ```
|
|
@@ -20,7 +20,7 @@ export type { CitationVariant } from "./types.js";
|
|
|
20
20
|
* ```tsx
|
|
21
21
|
* <CitationComponent
|
|
22
22
|
* citation={{ citationNumber: 1, value: "25% growth" }}
|
|
23
|
-
*
|
|
23
|
+
* verification={verificationResult}
|
|
24
24
|
* variant="numeric"
|
|
25
25
|
* />
|
|
26
26
|
* // Renders: 1✓
|
|
@@ -30,7 +30,7 @@ export type { CitationVariant } from "./types.js";
|
|
|
30
30
|
* ```tsx
|
|
31
31
|
* <CitationComponent
|
|
32
32
|
* citation={{ citationNumber: 1, value: "25% growth" }}
|
|
33
|
-
*
|
|
33
|
+
* verification={verificationResult}
|
|
34
34
|
* variant="text"
|
|
35
35
|
* />
|
|
36
36
|
* // Renders: 25% growth✓
|
|
@@ -40,7 +40,7 @@ export type { CitationVariant } from "./types.js";
|
|
|
40
40
|
* ```tsx
|
|
41
41
|
* <CitationComponent
|
|
42
42
|
* citation={citation}
|
|
43
|
-
*
|
|
43
|
+
* verification={verificationResult}
|
|
44
44
|
* variant="minimal"
|
|
45
45
|
* />
|
|
46
46
|
* // Renders: Revenue grew...✓
|
|
@@ -50,7 +50,7 @@ export type { CitationVariant } from "./types.js";
|
|
|
50
50
|
* ```tsx
|
|
51
51
|
* <CitationComponent
|
|
52
52
|
* citation={citation}
|
|
53
|
-
*
|
|
53
|
+
* verification={verificationResult}
|
|
54
54
|
* variant="indicator"
|
|
55
55
|
* />
|
|
56
56
|
* // Renders: ✓
|
|
@@ -60,7 +60,7 @@ export type { CitationVariant } from "./types.js";
|
|
|
60
60
|
* ```tsx
|
|
61
61
|
* <CitationComponent
|
|
62
62
|
* citation={citation}
|
|
63
|
-
*
|
|
63
|
+
* verification={verificationResult}
|
|
64
64
|
* popoverPosition="hidden"
|
|
65
65
|
* />
|
|
66
66
|
* ```
|
|
@@ -70,7 +70,7 @@ export interface CitationComponentProps extends BaseCitationProps {
|
|
|
70
70
|
* Verification result from the DeepCitation API.
|
|
71
71
|
* Contains match snippet, page number, and verification image.
|
|
72
72
|
*/
|
|
73
|
-
|
|
73
|
+
verification?: Verification | null;
|
|
74
74
|
/**
|
|
75
75
|
* Display variant for the citation.
|
|
76
76
|
* - `brackets`: Shows value/number in brackets, blue text styling (default)
|
|
@@ -110,7 +110,7 @@ export interface CitationComponentProps extends BaseCitationProps {
|
|
|
110
110
|
*/
|
|
111
111
|
renderPopoverContent?: (props: {
|
|
112
112
|
citation: BaseCitationProps["citation"];
|
|
113
|
-
|
|
113
|
+
verification: Verification | null;
|
|
114
114
|
status: CitationStatus;
|
|
115
115
|
}) => ReactNode;
|
|
116
116
|
}
|
|
@@ -74,12 +74,12 @@ function getFoundStatusClass(status) {
|
|
|
74
74
|
* Status tooltip content for miss/partial states.
|
|
75
75
|
* Shows explanation when hovering over citations with issues.
|
|
76
76
|
*/
|
|
77
|
-
const StatusTooltipContent = ({ citation, status,
|
|
77
|
+
const StatusTooltipContent = ({ citation, status, verification, isExpanded, onToggleExpand, }) => {
|
|
78
78
|
const { isMiss, isPartialMatch } = status;
|
|
79
79
|
if (!isMiss && !isPartialMatch)
|
|
80
80
|
return null;
|
|
81
|
-
// Get search attempts from
|
|
82
|
-
const searchAttempts =
|
|
81
|
+
// Get search attempts from verification
|
|
82
|
+
const searchAttempts = verification?.searchState?.searchAttempts;
|
|
83
83
|
const failedAttempts = searchAttempts?.filter((a) => !a.success) || [];
|
|
84
84
|
// Collect all unique phrases tried
|
|
85
85
|
const allPhrases = [];
|
|
@@ -105,7 +105,7 @@ const StatusTooltipContent = ({ citation, status, foundCitation, isExpanded, onT
|
|
|
105
105
|
}
|
|
106
106
|
if (isPartialMatch) {
|
|
107
107
|
const expectedText = citation.fullPhrase || citation.value || "";
|
|
108
|
-
const actualText =
|
|
108
|
+
const actualText = verification?.matchSnippet || "";
|
|
109
109
|
const truncatedExpected = expectedText.length > 100
|
|
110
110
|
? expectedText.slice(0, 100) + "…"
|
|
111
111
|
: expectedText;
|
|
@@ -140,28 +140,28 @@ const ImageOverlay = ({ src, alt, onClose, }) => {
|
|
|
140
140
|
* Default popover content component.
|
|
141
141
|
* Shows verification image if available, otherwise shows text info.
|
|
142
142
|
*/
|
|
143
|
-
const DefaultPopoverContent = ({
|
|
144
|
-
const hasImage =
|
|
143
|
+
const DefaultPopoverContent = ({ verification, status, onImageClick, }) => {
|
|
144
|
+
const hasImage = verification?.verificationImageBase64;
|
|
145
145
|
const handleImageClick = useCallback((e) => {
|
|
146
146
|
e.preventDefault();
|
|
147
147
|
e.stopPropagation();
|
|
148
148
|
if (hasImage && onImageClick) {
|
|
149
|
-
onImageClick(
|
|
149
|
+
onImageClick(verification.verificationImageBase64);
|
|
150
150
|
}
|
|
151
|
-
}, [hasImage,
|
|
151
|
+
}, [hasImage, verification?.verificationImageBase64, onImageClick]);
|
|
152
152
|
// If we have a verification image, show only the image
|
|
153
153
|
if (hasImage) {
|
|
154
|
-
return (_jsx("button", { type: "button", className: "dc-popover-image-button", onClick: handleImageClick, "aria-label": "Click to view full size", children: _jsx("img", { src:
|
|
154
|
+
return (_jsx("button", { type: "button", className: "dc-popover-image-button", onClick: handleImageClick, "aria-label": "Click to view full size", children: _jsx("img", { src: verification.verificationImageBase64, alt: "Citation verification", className: "dc-popover-image", loading: "lazy" }) }));
|
|
155
155
|
}
|
|
156
156
|
// No image - show text info
|
|
157
157
|
const statusLabel = getStatusLabel(status);
|
|
158
158
|
const statusClass = getPopoverStatusClass(status);
|
|
159
|
-
const hasSnippet =
|
|
160
|
-
const pageNumber =
|
|
159
|
+
const hasSnippet = verification?.matchSnippet;
|
|
160
|
+
const pageNumber = verification?.pageNumber;
|
|
161
161
|
if (!hasSnippet && !statusLabel) {
|
|
162
162
|
return null;
|
|
163
163
|
}
|
|
164
|
-
return (_jsxs(_Fragment, { children: [statusLabel && (_jsx("span", { className: classNames("dc-popover-status", statusClass), children: statusLabel })), hasSnippet && (_jsxs("span", { className: "dc-popover-snippet", children: ["\"",
|
|
164
|
+
return (_jsxs(_Fragment, { children: [statusLabel && (_jsx("span", { className: classNames("dc-popover-status", statusClass), children: statusLabel })), hasSnippet && (_jsxs("span", { className: "dc-popover-snippet", children: ["\"", verification.matchSnippet, "\""] })), pageNumber && pageNumber > 0 && (_jsxs("span", { className: "dc-popover-page", children: ["Page ", pageNumber] }))] }));
|
|
165
165
|
};
|
|
166
166
|
// =============================================================================
|
|
167
167
|
// MAIN COMPONENT
|
|
@@ -182,7 +182,7 @@ const DefaultPopoverContent = ({ foundCitation, status, onImageClick, }) => {
|
|
|
182
182
|
* This means partial matches have blue text (because they were found) but
|
|
183
183
|
* an orange indicator (because they didn't match exactly).
|
|
184
184
|
*/
|
|
185
|
-
export const CitationComponent = forwardRef(({ citation, children, className, displayCitationValue = false, fallbackDisplay,
|
|
185
|
+
export const CitationComponent = forwardRef(({ citation, children, className, displayCitationValue = false, fallbackDisplay, verification, variant = "brackets", eventHandlers, isMobile = false, renderIndicator, renderContent, popoverPosition = "top", renderPopoverContent, }, ref) => {
|
|
186
186
|
const containerRef = useRef(null);
|
|
187
187
|
const wrapperRef = useRef(null);
|
|
188
188
|
const [expandedImageSrc, setExpandedImageSrc] = useState(null);
|
|
@@ -235,7 +235,7 @@ export const CitationComponent = forwardRef(({ citation, children, className, di
|
|
|
235
235
|
e.preventDefault();
|
|
236
236
|
e.stopPropagation();
|
|
237
237
|
// If we have a verification image
|
|
238
|
-
if (
|
|
238
|
+
if (verification?.verificationImageBase64) {
|
|
239
239
|
if (expandedImageSrc) {
|
|
240
240
|
// Image is open - close it and unpin
|
|
241
241
|
setExpandedImageSrc(null);
|
|
@@ -243,7 +243,7 @@ export const CitationComponent = forwardRef(({ citation, children, className, di
|
|
|
243
243
|
}
|
|
244
244
|
else if (isTooltipExpanded) {
|
|
245
245
|
// Already pinned - second click expands image
|
|
246
|
-
setExpandedImageSrc(
|
|
246
|
+
setExpandedImageSrc(verification.verificationImageBase64);
|
|
247
247
|
}
|
|
248
248
|
else {
|
|
249
249
|
// First click - just pin the popover open
|
|
@@ -260,11 +260,11 @@ export const CitationComponent = forwardRef(({ citation, children, className, di
|
|
|
260
260
|
eventHandlers,
|
|
261
261
|
citation,
|
|
262
262
|
citationKey,
|
|
263
|
-
|
|
263
|
+
verification?.verificationImageBase64,
|
|
264
264
|
expandedImageSrc,
|
|
265
265
|
isTooltipExpanded,
|
|
266
266
|
]);
|
|
267
|
-
const status = getCitationStatus(
|
|
267
|
+
const status = getCitationStatus(verification ?? null);
|
|
268
268
|
// const { isVerified, isPending } = status;
|
|
269
269
|
const { isMiss, isPartialMatch, isVerified, isPending } = status;
|
|
270
270
|
const displayText = useMemo(() => {
|
|
@@ -359,19 +359,19 @@ export const CitationComponent = forwardRef(({ citation, children, className, di
|
|
|
359
359
|
// Determine if popover should be shown
|
|
360
360
|
const isPopoverHidden = popoverPosition === "hidden";
|
|
361
361
|
const shouldShowPopover = !isPopoverHidden &&
|
|
362
|
-
|
|
363
|
-
(
|
|
362
|
+
verification &&
|
|
363
|
+
(verification.verificationImageBase64 || verification.matchSnippet);
|
|
364
364
|
// Determine if status tooltip should be shown (miss/partial without full verification)
|
|
365
365
|
const shouldShowStatusTooltip = !isPopoverHidden && (isMiss || isPartialMatch) && !shouldShowPopover;
|
|
366
366
|
// Popover content - determine position class (only "top" or "bottom" add classes)
|
|
367
367
|
const popoverPositionClass = popoverPosition === "bottom" ? "dc-popover--bottom" : "";
|
|
368
368
|
const popoverContent = shouldShowPopover ? (_jsx("span", { className: classNames("dc-popover", popoverPositionClass), children: renderPopoverContent ? (renderPopoverContent({
|
|
369
369
|
citation,
|
|
370
|
-
|
|
370
|
+
verification: verification ?? null,
|
|
371
371
|
status,
|
|
372
|
-
})) : (_jsx(DefaultPopoverContent, { citation: citation,
|
|
372
|
+
})) : (_jsx(DefaultPopoverContent, { citation: citation, verification: verification ?? null, status: status, onImageClick: handleImageClick })) })) : null;
|
|
373
373
|
// Status tooltip for miss/partial explanations
|
|
374
|
-
const statusTooltipContent = shouldShowStatusTooltip ? (_jsx(StatusTooltipContent, { citation: citation, status: status,
|
|
374
|
+
const statusTooltipContent = shouldShowStatusTooltip ? (_jsx(StatusTooltipContent, { citation: citation, status: status, verification: verification ?? null, isExpanded: isPhrasesExpanded, onToggleExpand: handleTogglePhrases })) : null;
|
|
375
375
|
const citationTrigger = (_jsx("span", { ref: (node) => {
|
|
376
376
|
containerRef.current =
|
|
377
377
|
node;
|
|
@@ -381,7 +381,7 @@ export const CitationComponent = forwardRef(({ citation, children, className, di
|
|
|
381
381
|
else if (ref) {
|
|
382
382
|
ref.current = node;
|
|
383
383
|
}
|
|
384
|
-
}, "data-citation-id": citationKey, "data-citation-instance": citationInstanceId, "data-tooltip-expanded": isTooltipExpanded, "data-has-image": !!
|
|
384
|
+
}, "data-citation-id": citationKey, "data-citation-instance": citationInstanceId, "data-tooltip-expanded": isTooltipExpanded, "data-has-image": !!verification?.verificationImageBase64, className: classNames("dc-citation", `dc-citation--${variant}`, foundStatusClass, className), onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onClick: handleToggleTooltip, onTouchEndCapture: isMobile ? handleTouchEnd : undefined, "aria-label": displayText ? `[${displayText}]` : undefined, "aria-expanded": isTooltipExpanded, children: renderCitationContent() }));
|
|
385
385
|
// Image overlay for full-size view
|
|
386
386
|
const imageOverlay = expandedImageSrc ? (_jsx(ImageOverlay, { src: expandedImageSrc, alt: "Citation verification - full size", onClose: handleCloseOverlay })) : null;
|
|
387
387
|
// Wrap with popover or status tooltip if needed
|
|
@@ -8,7 +8,7 @@ import type { BaseCitationProps, CitationVariant as CitationVariantType, Citatio
|
|
|
8
8
|
*/
|
|
9
9
|
export interface CitationVariantProps extends BaseCitationProps {
|
|
10
10
|
/** Found citation highlight location data */
|
|
11
|
-
|
|
11
|
+
verification?: Verification | null;
|
|
12
12
|
/** Current search state for the citation */
|
|
13
13
|
searchState?: SearchState | null;
|
|
14
14
|
/** Event handlers */
|
|
@@ -38,7 +38,7 @@ export interface ChipCitationProps extends CitationVariantProps {
|
|
|
38
38
|
*
|
|
39
39
|
* @example
|
|
40
40
|
* ```tsx
|
|
41
|
-
* <ChipCitation citation={citation}
|
|
41
|
+
* <ChipCitation citation={citation} verification={found} size="md" />
|
|
42
42
|
* ```
|
|
43
43
|
*/
|
|
44
44
|
export declare const ChipCitation: React.ForwardRefExoticComponent<ChipCitationProps & React.RefAttributes<HTMLSpanElement>>;
|
|
@@ -52,7 +52,7 @@ export interface SuperscriptCitationProps extends CitationVariantProps {
|
|
|
52
52
|
*
|
|
53
53
|
* @example
|
|
54
54
|
* ```tsx
|
|
55
|
-
* <SuperscriptCitation citation={citation}
|
|
55
|
+
* <SuperscriptCitation citation={citation} verification={found} />
|
|
56
56
|
* // Renders: Text content¹
|
|
57
57
|
* ```
|
|
58
58
|
*/
|
|
@@ -5,13 +5,13 @@ import { generateCitationKey, generateCitationInstanceId, getCitationDisplayText
|
|
|
5
5
|
const TWO_DOTS_THINKING_CONTENT = "..";
|
|
6
6
|
/**
|
|
7
7
|
* Hook to get common citation data.
|
|
8
|
-
* NOTE: Status is not memoized because
|
|
8
|
+
* NOTE: Status is not memoized because verification may be mutated in place.
|
|
9
9
|
*/
|
|
10
|
-
function useCitationData(citation,
|
|
10
|
+
function useCitationData(citation, verification) {
|
|
11
11
|
const citationKey = useMemo(() => generateCitationKey(citation), [citation]);
|
|
12
12
|
const citationInstanceId = useMemo(() => generateCitationInstanceId(citationKey), [citationKey]);
|
|
13
13
|
// Don't memoize - object reference as dependency causes stale values on mutation
|
|
14
|
-
const status = getCitationStatus(
|
|
14
|
+
const status = getCitationStatus(verification ?? null);
|
|
15
15
|
return { citationKey, citationInstanceId, status };
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
@@ -28,11 +28,11 @@ const DefaultPartialIndicator = () => (_jsx("span", { className: "citation-parti
|
|
|
28
28
|
*
|
|
29
29
|
* @example
|
|
30
30
|
* ```tsx
|
|
31
|
-
* <ChipCitation citation={citation}
|
|
31
|
+
* <ChipCitation citation={citation} verification={found} size="md" />
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
|
-
export const ChipCitation = forwardRef(({ citation, children, className, displayCitationValue = false, fallbackDisplay,
|
|
35
|
-
const { citationKey, citationInstanceId, status } = useCitationData(citation,
|
|
34
|
+
export const ChipCitation = forwardRef(({ citation, children, className, displayCitationValue = false, fallbackDisplay, verification, eventHandlers, isMobile = false, preventTooltips = false, pendingContent = TWO_DOTS_THINKING_CONTENT, renderVerifiedIndicator = () => _jsx(DefaultVerifiedIndicator, {}), renderPartialIndicator = () => _jsx(DefaultPartialIndicator, {}), size = "md", showIcon = false, icon, }, ref) => {
|
|
35
|
+
const { citationKey, citationInstanceId, status } = useCitationData(citation, verification);
|
|
36
36
|
const { isVerified, isMiss, isPartialMatch, isPending } = status;
|
|
37
37
|
const displayText = useMemo(() => getCitationDisplayText(citation, {
|
|
38
38
|
displayCitationValue,
|
|
@@ -77,12 +77,12 @@ ChipCitation.displayName = "ChipCitation";
|
|
|
77
77
|
*
|
|
78
78
|
* @example
|
|
79
79
|
* ```tsx
|
|
80
|
-
* <SuperscriptCitation citation={citation}
|
|
80
|
+
* <SuperscriptCitation citation={citation} verification={found} />
|
|
81
81
|
* // Renders: Text content¹
|
|
82
82
|
* ```
|
|
83
83
|
*/
|
|
84
|
-
export const SuperscriptCitation = forwardRef(({ citation, children, className, displayCitationValue = false, fallbackDisplay,
|
|
85
|
-
const { citationKey, citationInstanceId, status } = useCitationData(citation,
|
|
84
|
+
export const SuperscriptCitation = forwardRef(({ citation, children, className, displayCitationValue = false, fallbackDisplay, verification, eventHandlers, isMobile = false, preventTooltips = false, pendingContent = TWO_DOTS_THINKING_CONTENT, renderVerifiedIndicator = () => _jsx(DefaultVerifiedIndicator, {}), renderPartialIndicator = () => _jsx(DefaultPartialIndicator, {}), showBrackets = false, }, ref) => {
|
|
85
|
+
const { citationKey, citationInstanceId, status } = useCitationData(citation, verification);
|
|
86
86
|
const { isVerified, isMiss, isPartialMatch, isPending } = status;
|
|
87
87
|
const displayText = useMemo(() => getCitationDisplayText(citation, {
|
|
88
88
|
displayCitationValue,
|
|
@@ -123,8 +123,8 @@ const FOOTNOTE_SYMBOLS = ["*", "†", "‡", "§", "‖", "¶"];
|
|
|
123
123
|
* // Renders: Text content*
|
|
124
124
|
* ```
|
|
125
125
|
*/
|
|
126
|
-
export const FootnoteCitation = forwardRef(({ citation, children, className, fallbackDisplay,
|
|
127
|
-
const { citationKey, citationInstanceId, status } = useCitationData(citation,
|
|
126
|
+
export const FootnoteCitation = forwardRef(({ citation, children, className, fallbackDisplay, verification, eventHandlers, preventTooltips = false, pendingContent = TWO_DOTS_THINKING_CONTENT, renderVerifiedIndicator = () => _jsx(DefaultVerifiedIndicator, {}), renderPartialIndicator = () => _jsx(DefaultPartialIndicator, {}), symbolStyle = "number", customSymbol, }, ref) => {
|
|
127
|
+
const { citationKey, citationInstanceId, status } = useCitationData(citation, verification);
|
|
128
128
|
const { isVerified, isMiss, isPartialMatch, isPending } = status;
|
|
129
129
|
const displaySymbol = useMemo(() => {
|
|
130
130
|
if (symbolStyle === "custom" && customSymbol)
|
|
@@ -174,8 +174,8 @@ FootnoteCitation.displayName = "FootnoteCitation";
|
|
|
174
174
|
* ```
|
|
175
175
|
*/
|
|
176
176
|
export const InlineCitation = forwardRef(({ citation, children, className, displayCitationValue = true, // Default to merged for inline
|
|
177
|
-
fallbackDisplay,
|
|
178
|
-
const { citationKey, citationInstanceId, status } = useCitationData(citation,
|
|
177
|
+
fallbackDisplay, verification, eventHandlers, preventTooltips = false, pendingContent = TWO_DOTS_THINKING_CONTENT, renderVerifiedIndicator = () => _jsx(DefaultVerifiedIndicator, {}), renderPartialIndicator = () => _jsx(DefaultPartialIndicator, {}), underlineStyle = "dotted", }, ref) => {
|
|
178
|
+
const { citationKey, citationInstanceId, status } = useCitationData(citation, verification);
|
|
179
179
|
const { isVerified, isMiss, isPartialMatch, isPending } = status;
|
|
180
180
|
const displayText = useMemo(() => getCitationDisplayText(citation, {
|
|
181
181
|
displayCitationValue,
|
|
@@ -216,8 +216,8 @@ InlineCitation.displayName = "InlineCitation";
|
|
|
216
216
|
* // Renders: 1
|
|
217
217
|
* ```
|
|
218
218
|
*/
|
|
219
|
-
export const MinimalCitation = forwardRef(({ citation, children, className, displayCitationValue = false, fallbackDisplay,
|
|
220
|
-
const { citationKey, citationInstanceId, status } = useCitationData(citation,
|
|
219
|
+
export const MinimalCitation = forwardRef(({ citation, children, className, displayCitationValue = false, fallbackDisplay, verification, eventHandlers, preventTooltips = false, pendingContent = TWO_DOTS_THINKING_CONTENT, renderVerifiedIndicator = () => _jsx(DefaultVerifiedIndicator, {}), renderPartialIndicator = () => _jsx(DefaultPartialIndicator, {}), showStatusIndicator = true, }, ref) => {
|
|
220
|
+
const { citationKey, citationInstanceId, status } = useCitationData(citation, verification);
|
|
221
221
|
const { isVerified, isMiss, isPartialMatch, isPending } = status;
|
|
222
222
|
const displayText = useMemo(() => getCitationDisplayText(citation, {
|
|
223
223
|
displayCitationValue,
|
|
@@ -11,7 +11,7 @@ interface CitationContextValue {
|
|
|
11
11
|
citationKey: string;
|
|
12
12
|
citationInstanceId: string;
|
|
13
13
|
status: CitationStatus;
|
|
14
|
-
|
|
14
|
+
verification: Verification | null;
|
|
15
15
|
searchState: SearchState | null;
|
|
16
16
|
config: {
|
|
17
17
|
displayCitationValue: boolean;
|
|
@@ -25,7 +25,7 @@ export declare function useCitationContext(): CitationContextValue;
|
|
|
25
25
|
export declare function useCitationContextSafe(): CitationContextValue | null;
|
|
26
26
|
export interface CitationRootProps {
|
|
27
27
|
citation: CitationType;
|
|
28
|
-
|
|
28
|
+
verification?: Verification | null;
|
|
29
29
|
searchState?: SearchState | null;
|
|
30
30
|
children: ReactNode;
|
|
31
31
|
displayCitationValue?: boolean;
|
package/lib/react/primitives.js
CHANGED
|
@@ -20,16 +20,16 @@ export function useCitationContextSafe() {
|
|
|
20
20
|
return useContext(CitationContext);
|
|
21
21
|
}
|
|
22
22
|
/** Root component that provides citation context to all child primitives. */
|
|
23
|
-
export const CitationRoot = forwardRef(({ citation,
|
|
23
|
+
export const CitationRoot = forwardRef(({ citation, verification = null, searchState = null, children, displayCitationValue = false, fallbackDisplay = null, pendingContent = "..", className, ...props }, ref) => {
|
|
24
24
|
const citationKey = useMemo(() => generateCitationKey(citation), [citation]);
|
|
25
25
|
const citationInstanceId = useMemo(() => generateCitationInstanceId(citationKey), [citationKey]);
|
|
26
|
-
const status = getCitationStatus(
|
|
26
|
+
const status = getCitationStatus(verification);
|
|
27
27
|
const contextValue = useMemo(() => ({
|
|
28
28
|
citation,
|
|
29
29
|
citationKey,
|
|
30
30
|
citationInstanceId,
|
|
31
31
|
status,
|
|
32
|
-
|
|
32
|
+
verification,
|
|
33
33
|
searchState,
|
|
34
34
|
config: {
|
|
35
35
|
displayCitationValue,
|
|
@@ -41,7 +41,7 @@ export const CitationRoot = forwardRef(({ citation, foundCitation = null, search
|
|
|
41
41
|
citationKey,
|
|
42
42
|
citationInstanceId,
|
|
43
43
|
status,
|
|
44
|
-
|
|
44
|
+
verification,
|
|
45
45
|
searchState,
|
|
46
46
|
displayCitationValue,
|
|
47
47
|
fallbackDisplay,
|
package/lib/react/types.d.ts
CHANGED
|
@@ -130,7 +130,7 @@ export interface CitationContentProps extends BaseCitationProps {
|
|
|
130
130
|
/** Unique instance ID for this citation render */
|
|
131
131
|
citationInstanceId: string;
|
|
132
132
|
/** Found citation highlight data */
|
|
133
|
-
|
|
133
|
+
verification: Verification | null | undefined;
|
|
134
134
|
/** Current search state */
|
|
135
135
|
searchState: SearchState | undefined | null;
|
|
136
136
|
/** Actual page number where citation was found */
|
package/lib/react/utils.js
CHANGED
|
@@ -8,8 +8,8 @@ export function generateCitationKey(citation) {
|
|
|
8
8
|
citation.fileId || "",
|
|
9
9
|
citation.pageNumber?.toString() || "",
|
|
10
10
|
citation.fullPhrase || "",
|
|
11
|
+
citation.keySpan?.toString() || "",
|
|
11
12
|
citation.value || "",
|
|
12
|
-
citation.citationNumber?.toString() || "",
|
|
13
13
|
citation.lineIds?.join(",") || "",
|
|
14
14
|
citation.timestamps?.startTime || "",
|
|
15
15
|
citation.timestamps?.endTime || "",
|