@deepcitation/deepcitation-js 1.0.9 → 1.1.1
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 +2 -2
- package/lib/client/DeepCitation.d.ts +2 -2
- package/lib/client/DeepCitation.js +16 -10
- package/lib/client/types.d.ts +12 -4
- package/lib/index.d.ts +2 -2
- package/lib/index.js +1 -1
- package/lib/parsing/parseCitation.d.ts +3 -3
- package/lib/parsing/parseCitation.js +3 -3
- package/lib/react/CitationComponent.d.ts +9 -9
- package/lib/react/CitationComponent.js +55 -37
- package/lib/react/CitationVariants.d.ts +4 -4
- package/lib/react/CitationVariants.js +23 -22
- package/lib/react/primitives.d.ts +3 -3
- package/lib/react/primitives.js +12 -7
- package/lib/react/types.d.ts +3 -3
- package/lib/types/citation.d.ts +4 -4
- package/lib/types/index.d.ts +2 -2
- package/lib/types/index.js +1 -1
- package/lib/types/{foundHighlight.d.ts → verification.d.ts} +5 -5
- package/lib/types/verification.js +23 -0
- package/package.json +1 -1
- package/lib/types/foundHighlight.js +0 -22
package/README.md
CHANGED
|
@@ -107,7 +107,7 @@ function Response({ citations, verifications }) {
|
|
|
107
107
|
Revenue grew by
|
|
108
108
|
<CitationComponent
|
|
109
109
|
citation={citations["1"]}
|
|
110
|
-
|
|
110
|
+
verification={verifications["1"]}
|
|
111
111
|
/>
|
|
112
112
|
this quarter.
|
|
113
113
|
</p>
|
|
@@ -163,7 +163,7 @@ import {
|
|
|
163
163
|
```typescript
|
|
164
164
|
import type {
|
|
165
165
|
Citation,
|
|
166
|
-
|
|
166
|
+
Verification,
|
|
167
167
|
SearchState,
|
|
168
168
|
SearchStatus,
|
|
169
169
|
} from "@deepcitation/deepcitation-js";
|
|
@@ -154,7 +154,7 @@ export declare class DeepCitation {
|
|
|
154
154
|
* const citations = getAllCitationsFromLlmOutput(llmResponse);
|
|
155
155
|
* const verified = await dc.verifyCitations(fileId, citations);
|
|
156
156
|
*
|
|
157
|
-
* for (const [key, result] of Object.entries(verified.
|
|
157
|
+
* for (const [key, result] of Object.entries(verified.verifications)) {
|
|
158
158
|
* console.log(key, result.searchState?.status);
|
|
159
159
|
* // "found", "partial_text_found", "not_found", etc.
|
|
160
160
|
* }
|
|
@@ -175,7 +175,7 @@ export declare class DeepCitation {
|
|
|
175
175
|
* fileDataParts, // From prepareFiles()
|
|
176
176
|
* });
|
|
177
177
|
*
|
|
178
|
-
* for (const [key, result] of Object.entries(result.
|
|
178
|
+
* for (const [key, result] of Object.entries(result.verifications)) {
|
|
179
179
|
* console.log(key, result.searchState?.status);
|
|
180
180
|
* }
|
|
181
181
|
* ```
|
|
@@ -237,13 +237,19 @@ export class DeepCitation {
|
|
|
237
237
|
return { fileDataParts: [], deepTextPromptPortion: [] };
|
|
238
238
|
}
|
|
239
239
|
// Upload all files in parallel
|
|
240
|
-
const uploadPromises = files.map(({ file, filename, fileId }) => this.uploadFile(file, { filename, fileId }))
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
240
|
+
const uploadPromises = files.map(({ file, filename, fileId }) => this.uploadFile(file, { filename, fileId }).then((result) => ({
|
|
241
|
+
result,
|
|
242
|
+
filename,
|
|
243
|
+
})));
|
|
244
|
+
const uploadResults = await Promise.all(uploadPromises);
|
|
245
|
+
// Extract file data parts with deepTextPromptPortion included (single source of truth)
|
|
246
|
+
const fileDataParts = uploadResults.map(({ result, filename }) => ({
|
|
244
247
|
fileId: result.fileId,
|
|
248
|
+
deepTextPromptPortion: result.deepTextPromptPortion,
|
|
249
|
+
filename: filename || result.metadata?.filename,
|
|
245
250
|
}));
|
|
246
|
-
|
|
251
|
+
// Also return separate array for backwards compatibility (deprecated)
|
|
252
|
+
const deepTextPromptPortion = fileDataParts.map((part) => part.deepTextPromptPortion);
|
|
247
253
|
return { fileDataParts, deepTextPromptPortion };
|
|
248
254
|
}
|
|
249
255
|
/**
|
|
@@ -261,7 +267,7 @@ export class DeepCitation {
|
|
|
261
267
|
* const citations = getAllCitationsFromLlmOutput(llmResponse);
|
|
262
268
|
* const verified = await dc.verifyCitations(fileId, citations);
|
|
263
269
|
*
|
|
264
|
-
* for (const [key, result] of Object.entries(verified.
|
|
270
|
+
* for (const [key, result] of Object.entries(verified.verifications)) {
|
|
265
271
|
* console.log(key, result.searchState?.status);
|
|
266
272
|
* // "found", "partial_text_found", "not_found", etc.
|
|
267
273
|
* }
|
|
@@ -328,7 +334,7 @@ export class DeepCitation {
|
|
|
328
334
|
* fileDataParts, // From prepareFiles()
|
|
329
335
|
* });
|
|
330
336
|
*
|
|
331
|
-
* for (const [key, result] of Object.entries(result.
|
|
337
|
+
* for (const [key, result] of Object.entries(result.verifications)) {
|
|
332
338
|
* console.log(key, result.searchState?.status);
|
|
333
339
|
* }
|
|
334
340
|
* ```
|
|
@@ -340,7 +346,7 @@ export class DeepCitation {
|
|
|
340
346
|
citations = getAllCitationsFromLlmOutput(llmOutput);
|
|
341
347
|
// If no citations found, return empty result
|
|
342
348
|
if (Object.keys(citations).length === 0) {
|
|
343
|
-
return {
|
|
349
|
+
return { verifications: {} };
|
|
344
350
|
}
|
|
345
351
|
// Group citations by fileId
|
|
346
352
|
const citationsByFile = new Map();
|
|
@@ -361,8 +367,8 @@ export class DeepCitation {
|
|
|
361
367
|
const results = await Promise.all(verificationPromises);
|
|
362
368
|
const allHighlights = {};
|
|
363
369
|
for (const result of results) {
|
|
364
|
-
Object.assign(allHighlights, result.
|
|
370
|
+
Object.assign(allHighlights, result.verifications);
|
|
365
371
|
}
|
|
366
|
-
return {
|
|
372
|
+
return { verifications: allHighlights };
|
|
367
373
|
}
|
|
368
374
|
}
|
package/lib/client/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Citation,
|
|
1
|
+
import type { Citation, Verification } from "../types/index.js";
|
|
2
2
|
/**
|
|
3
3
|
* Configuration options for the DeepCitation client
|
|
4
4
|
*/
|
|
@@ -51,7 +51,7 @@ export interface UploadFileOptions {
|
|
|
51
51
|
*/
|
|
52
52
|
export interface VerifyCitationsResponse {
|
|
53
53
|
/** Map of citation keys to their verification results */
|
|
54
|
-
|
|
54
|
+
verifications: Record<string, Verification>;
|
|
55
55
|
}
|
|
56
56
|
/**
|
|
57
57
|
* Options for citation verification
|
|
@@ -81,14 +81,22 @@ export interface FileInput {
|
|
|
81
81
|
export interface FileDataPart {
|
|
82
82
|
/** The file ID assigned by DeepCitation */
|
|
83
83
|
fileId: string;
|
|
84
|
+
/** The formatted text content for LLM prompts (with page markers and line IDs) */
|
|
85
|
+
deepTextPromptPortion: string;
|
|
86
|
+
/** Optional filename for display purposes */
|
|
87
|
+
filename?: string;
|
|
84
88
|
}
|
|
85
89
|
/**
|
|
86
90
|
* Result from prepareFiles
|
|
87
91
|
*/
|
|
88
92
|
export interface PrepareFilesResult {
|
|
89
|
-
/** Array of file references for verification */
|
|
93
|
+
/** Array of file references for verification (includes deepTextPromptPortion for each file) */
|
|
90
94
|
fileDataParts: FileDataPart[];
|
|
91
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* Array of formatted text content for LLM prompts (with page markers and line IDs).
|
|
97
|
+
* @deprecated Use fileDataParts[].deepTextPromptPortion instead for single source of truth.
|
|
98
|
+
* This is kept for backwards compatibility but will be removed in a future version.
|
|
99
|
+
*/
|
|
92
100
|
deepTextPromptPortion: string[];
|
|
93
101
|
}
|
|
94
102
|
/**
|
package/lib/index.d.ts
CHANGED
|
@@ -9,8 +9,8 @@ export { normalizeCitations, getCitationPageNumber, } from "./parsing/normalizeC
|
|
|
9
9
|
export { isGeminiGarbage, cleanRepeatingLastSentence, } from "./parsing/parseWorkAround.js";
|
|
10
10
|
export type { Citation, CitationStatus, VerifyCitationRequest, VerifyCitationResponse, OutputImageFormat, } from "./types/citation.js";
|
|
11
11
|
export { DEFAULT_OUTPUT_IMAGE_FORMAT } from "./types/citation.js";
|
|
12
|
-
export type {
|
|
13
|
-
export {
|
|
12
|
+
export type { Verification } from "./types/verification.js";
|
|
13
|
+
export { NOT_FOUND_VERIFICATION_INDEX, PENDING_VERIFICATION_INDEX, BLANK_VERIFICATION, deterministicIdFromVerification, } from "./types/verification.js";
|
|
14
14
|
export type { SearchState, SearchStatus, SearchMethod, SearchAttempt, } from "./types/search.js";
|
|
15
15
|
export type { ScreenBox, PdfSpaceItem, IVertex } from "./types/boxes.js";
|
|
16
16
|
export { sha1Hash } from "./utils/sha.js";
|
package/lib/index.js
CHANGED
|
@@ -9,7 +9,7 @@ export { parseCitation, getCitationStatus, getAllCitationsFromLlmOutput, groupCi
|
|
|
9
9
|
export { normalizeCitations, getCitationPageNumber, } from "./parsing/normalizeCitation.js";
|
|
10
10
|
export { isGeminiGarbage, cleanRepeatingLastSentence, } from "./parsing/parseWorkAround.js";
|
|
11
11
|
export { DEFAULT_OUTPUT_IMAGE_FORMAT } from "./types/citation.js";
|
|
12
|
-
export {
|
|
12
|
+
export { NOT_FOUND_VERIFICATION_INDEX, PENDING_VERIFICATION_INDEX, BLANK_VERIFICATION, deterministicIdFromVerification, } from "./types/verification.js";
|
|
13
13
|
// Utilities
|
|
14
14
|
export { sha1Hash } from "./utils/sha.js";
|
|
15
15
|
export { generateCitationKey } from "./react/utils.js";
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Verification } from "../types/verification.js";
|
|
2
2
|
import { type Citation, type CitationStatus } from "../types/citation.js";
|
|
3
3
|
/**
|
|
4
4
|
* Calculates the verification status of a citation based on the found highlight and search state.
|
|
5
5
|
*
|
|
6
|
-
* @param
|
|
6
|
+
* @param verification - The found highlight location, or null/undefined if not found
|
|
7
7
|
* @returns An object containing boolean flags for verification status
|
|
8
8
|
*/
|
|
9
|
-
export declare function getCitationStatus(
|
|
9
|
+
export declare function getCitationStatus(verification: Verification | null | undefined): CitationStatus;
|
|
10
10
|
export declare const parseCitation: (fragment: string, mdAttachmentId?: string | null, citationCounterRef?: any | null, isVerbose?: boolean) => {
|
|
11
11
|
beforeCite: string;
|
|
12
12
|
afterCite: string;
|
|
@@ -48,11 +48,11 @@ function parseLineIds(lineIdsString) {
|
|
|
48
48
|
/**
|
|
49
49
|
* Calculates the verification status of a citation based on the found highlight and search state.
|
|
50
50
|
*
|
|
51
|
-
* @param
|
|
51
|
+
* @param verification - The found highlight location, or null/undefined if not found
|
|
52
52
|
* @returns An object containing boolean flags for verification status
|
|
53
53
|
*/
|
|
54
|
-
export function getCitationStatus(
|
|
55
|
-
const searchState =
|
|
54
|
+
export function getCitationStatus(verification) {
|
|
55
|
+
const searchState = verification?.searchState;
|
|
56
56
|
const isMiss = searchState?.status === "not_found";
|
|
57
57
|
const isFullMatchWithMissedValue = searchState?.status === "found_phrase_missed_value";
|
|
58
58
|
const isFoundValueMissedFullMatch = searchState?.status === "found_value_only";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { type ReactNode } from "react";
|
|
2
2
|
import { type CitationStatus } from "../types/citation.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { Verification } from "../types/verification.js";
|
|
4
4
|
import type { BaseCitationProps, CitationEventHandlers, CitationRenderProps, CitationVariant } from "./types.js";
|
|
5
5
|
import "./styles.css";
|
|
6
6
|
export type { CitationVariant } from "./types.js";
|
|
@@ -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
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { forwardRef, memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { forwardRef, memo, useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
3
3
|
import { createPortal } from "react-dom";
|
|
4
4
|
import { CheckIcon, WarningIcon } from "./icons.js";
|
|
5
|
-
import { classNames, generateCitationInstanceId, generateCitationKey, getCitationDisplayText } from "./utils.js";
|
|
5
|
+
import { classNames, generateCitationInstanceId, generateCitationKey, getCitationDisplayText, } from "./utils.js";
|
|
6
6
|
import { getCitationStatus } from "../parsing/parseCitation.js";
|
|
7
7
|
import "./styles.css";
|
|
8
8
|
const TWO_DOTS_THINKING_CONTENT = "..";
|
|
@@ -74,13 +74,13 @@ 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 =
|
|
83
|
-
const failedAttempts = searchAttempts?.filter(a => !a.success) || [];
|
|
81
|
+
// Get search attempts from verification
|
|
82
|
+
const searchAttempts = verification?.searchState?.searchAttempts;
|
|
83
|
+
const failedAttempts = searchAttempts?.filter((a) => !a.success) || [];
|
|
84
84
|
// Collect all unique phrases tried
|
|
85
85
|
const allPhrases = [];
|
|
86
86
|
const seenPhrases = new Set();
|
|
@@ -105,8 +105,10 @@ const StatusTooltipContent = ({ citation, status, foundCitation, isExpanded, onT
|
|
|
105
105
|
}
|
|
106
106
|
if (isPartialMatch) {
|
|
107
107
|
const expectedText = citation.fullPhrase || citation.value || "";
|
|
108
|
-
const actualText =
|
|
109
|
-
const truncatedExpected = expectedText.length > 100
|
|
108
|
+
const actualText = verification?.matchSnippet || "";
|
|
109
|
+
const truncatedExpected = expectedText.length > 100
|
|
110
|
+
? expectedText.slice(0, 100) + "…"
|
|
111
|
+
: expectedText;
|
|
110
112
|
const truncatedActual = actualText.length > 100 ? actualText.slice(0, 100) + "…" : actualText;
|
|
111
113
|
return (_jsxs("span", { className: "dc-status-tooltip", role: "tooltip", children: [_jsxs("span", { className: "dc-status-header dc-status-header--partial", children: [_jsx(WarningIcon, {}), _jsx("span", { children: "Partial match" })] }), _jsx("span", { className: "dc-status-description", children: "Text differs from citation." }), truncatedExpected && (_jsxs("span", { className: "dc-status-searched", children: [_jsx("span", { className: "dc-status-label", children: "Expected" }), _jsx("span", { className: "dc-status-text", children: truncatedExpected })] })), truncatedActual && (_jsxs("span", { className: "dc-status-searched", children: [_jsx("span", { className: "dc-status-label", children: "Found" }), _jsx("span", { className: "dc-status-text", children: truncatedActual })] }))] }));
|
|
112
114
|
}
|
|
@@ -116,7 +118,7 @@ const StatusTooltipContent = ({ citation, status, foundCitation, isExpanded, onT
|
|
|
116
118
|
* Full-size image overlay component.
|
|
117
119
|
* Uses portal to render at document body level.
|
|
118
120
|
*/
|
|
119
|
-
const ImageOverlay = ({ src, alt, onClose }) => {
|
|
121
|
+
const ImageOverlay = ({ src, alt, onClose, }) => {
|
|
120
122
|
const handleBackdropClick = useCallback((e) => {
|
|
121
123
|
if (e.target === e.currentTarget) {
|
|
122
124
|
onClose();
|
|
@@ -138,28 +140,28 @@ const ImageOverlay = ({ src, alt, onClose }) => {
|
|
|
138
140
|
* Default popover content component.
|
|
139
141
|
* Shows verification image if available, otherwise shows text info.
|
|
140
142
|
*/
|
|
141
|
-
const DefaultPopoverContent = ({
|
|
142
|
-
const hasImage =
|
|
143
|
+
const DefaultPopoverContent = ({ verification, status, onImageClick, }) => {
|
|
144
|
+
const hasImage = verification?.verificationImageBase64;
|
|
143
145
|
const handleImageClick = useCallback((e) => {
|
|
144
146
|
e.preventDefault();
|
|
145
147
|
e.stopPropagation();
|
|
146
148
|
if (hasImage && onImageClick) {
|
|
147
|
-
onImageClick(
|
|
149
|
+
onImageClick(verification.verificationImageBase64);
|
|
148
150
|
}
|
|
149
|
-
}, [hasImage,
|
|
151
|
+
}, [hasImage, verification?.verificationImageBase64, onImageClick]);
|
|
150
152
|
// If we have a verification image, show only the image
|
|
151
153
|
if (hasImage) {
|
|
152
|
-
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" }) }));
|
|
153
155
|
}
|
|
154
156
|
// No image - show text info
|
|
155
157
|
const statusLabel = getStatusLabel(status);
|
|
156
158
|
const statusClass = getPopoverStatusClass(status);
|
|
157
|
-
const hasSnippet =
|
|
158
|
-
const pageNumber =
|
|
159
|
+
const hasSnippet = verification?.matchSnippet;
|
|
160
|
+
const pageNumber = verification?.pageNumber;
|
|
159
161
|
if (!hasSnippet && !statusLabel) {
|
|
160
162
|
return null;
|
|
161
163
|
}
|
|
162
|
-
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] }))] }));
|
|
163
165
|
};
|
|
164
166
|
// =============================================================================
|
|
165
167
|
// MAIN COMPONENT
|
|
@@ -180,7 +182,7 @@ const DefaultPopoverContent = ({ foundCitation, status, onImageClick, }) => {
|
|
|
180
182
|
* This means partial matches have blue text (because they were found) but
|
|
181
183
|
* an orange indicator (because they didn't match exactly).
|
|
182
184
|
*/
|
|
183
|
-
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) => {
|
|
184
186
|
const containerRef = useRef(null);
|
|
185
187
|
const wrapperRef = useRef(null);
|
|
186
188
|
const [expandedImageSrc, setExpandedImageSrc] = useState(null);
|
|
@@ -195,14 +197,15 @@ export const CitationComponent = forwardRef(({ citation, children, className, di
|
|
|
195
197
|
const handleTogglePhrases = useCallback((e) => {
|
|
196
198
|
e?.preventDefault();
|
|
197
199
|
e?.stopPropagation();
|
|
198
|
-
setIsPhrasesExpanded(prev => !prev);
|
|
200
|
+
setIsPhrasesExpanded((prev) => !prev);
|
|
199
201
|
}, []);
|
|
200
202
|
// Handle click outside to close expanded tooltip
|
|
201
203
|
useEffect(() => {
|
|
202
204
|
if (!isTooltipExpanded)
|
|
203
205
|
return;
|
|
204
206
|
const handleClickOutside = (event) => {
|
|
205
|
-
if (wrapperRef.current &&
|
|
207
|
+
if (wrapperRef.current &&
|
|
208
|
+
!wrapperRef.current.contains(event.target)) {
|
|
206
209
|
setIsTooltipExpanded(false);
|
|
207
210
|
}
|
|
208
211
|
};
|
|
@@ -232,7 +235,7 @@ export const CitationComponent = forwardRef(({ citation, children, className, di
|
|
|
232
235
|
e.preventDefault();
|
|
233
236
|
e.stopPropagation();
|
|
234
237
|
// If we have a verification image
|
|
235
|
-
if (
|
|
238
|
+
if (verification?.verificationImageBase64) {
|
|
236
239
|
if (expandedImageSrc) {
|
|
237
240
|
// Image is open - close it and unpin
|
|
238
241
|
setExpandedImageSrc(null);
|
|
@@ -240,7 +243,7 @@ export const CitationComponent = forwardRef(({ citation, children, className, di
|
|
|
240
243
|
}
|
|
241
244
|
else if (isTooltipExpanded) {
|
|
242
245
|
// Already pinned - second click expands image
|
|
243
|
-
setExpandedImageSrc(
|
|
246
|
+
setExpandedImageSrc(verification.verificationImageBase64);
|
|
244
247
|
}
|
|
245
248
|
else {
|
|
246
249
|
// First click - just pin the popover open
|
|
@@ -249,19 +252,19 @@ export const CitationComponent = forwardRef(({ citation, children, className, di
|
|
|
249
252
|
}
|
|
250
253
|
else {
|
|
251
254
|
// No image - toggle phrases expansion for miss/partial tooltips
|
|
252
|
-
setIsTooltipExpanded(prev => !prev);
|
|
253
|
-
setIsPhrasesExpanded(prev => !prev);
|
|
255
|
+
setIsTooltipExpanded((prev) => !prev);
|
|
256
|
+
setIsPhrasesExpanded((prev) => !prev);
|
|
254
257
|
}
|
|
255
258
|
eventHandlers?.onClick?.(citation, citationKey, e);
|
|
256
259
|
}, [
|
|
257
260
|
eventHandlers,
|
|
258
261
|
citation,
|
|
259
262
|
citationKey,
|
|
260
|
-
|
|
263
|
+
verification?.verificationImageBase64,
|
|
261
264
|
expandedImageSrc,
|
|
262
265
|
isTooltipExpanded,
|
|
263
266
|
]);
|
|
264
|
-
const status = getCitationStatus(
|
|
267
|
+
const status = getCitationStatus(verification ?? null);
|
|
265
268
|
// const { isVerified, isPending } = status;
|
|
266
269
|
const { isMiss, isPartialMatch, isVerified, isPending } = status;
|
|
267
270
|
const displayText = useMemo(() => {
|
|
@@ -271,7 +274,10 @@ export const CitationComponent = forwardRef(({ citation, children, className, di
|
|
|
271
274
|
}
|
|
272
275
|
// For text/minimal/brackets, show the value or fullPhrase
|
|
273
276
|
return getCitationDisplayText(citation, {
|
|
274
|
-
displayCitationValue: variant === "text" ||
|
|
277
|
+
displayCitationValue: variant === "text" ||
|
|
278
|
+
variant === "minimal" ||
|
|
279
|
+
variant === "brackets" ||
|
|
280
|
+
displayCitationValue,
|
|
275
281
|
fallbackDisplay,
|
|
276
282
|
});
|
|
277
283
|
}, [citation, variant, displayCitationValue, fallbackDisplay]);
|
|
@@ -292,8 +298,11 @@ export const CitationComponent = forwardRef(({ citation, children, className, di
|
|
|
292
298
|
}
|
|
293
299
|
}, [eventHandlers, citation, citationKey, isMobile]);
|
|
294
300
|
// Early return for miss with fallback display
|
|
295
|
-
if (fallbackDisplay !== null &&
|
|
296
|
-
|
|
301
|
+
if (fallbackDisplay !== null &&
|
|
302
|
+
fallbackDisplay !== undefined &&
|
|
303
|
+
displayCitationValue &&
|
|
304
|
+
isMiss) {
|
|
305
|
+
return (_jsx("span", { className: classNames("dc-citation-fallback", className), children: fallbackDisplay }));
|
|
297
306
|
}
|
|
298
307
|
// Render the appropriate indicator based on match quality
|
|
299
308
|
const renderStatusIndicator = () => {
|
|
@@ -323,12 +332,14 @@ export const CitationComponent = forwardRef(({ citation, children, className, di
|
|
|
323
332
|
status,
|
|
324
333
|
citationKey,
|
|
325
334
|
displayText,
|
|
326
|
-
isMergedDisplay: variant === "text" ||
|
|
335
|
+
isMergedDisplay: variant === "text" ||
|
|
336
|
+
variant === "brackets" ||
|
|
337
|
+
displayCitationValue,
|
|
327
338
|
});
|
|
328
339
|
}
|
|
329
340
|
// Indicator-only variant - just the checkmark/warning
|
|
330
341
|
if (variant === "indicator") {
|
|
331
|
-
return _jsx("span", { className: "dc-citation-text", children: renderStatusIndicator() });
|
|
342
|
+
return (_jsx("span", { className: "dc-citation-text", children: renderStatusIndicator() }));
|
|
332
343
|
}
|
|
333
344
|
// Text variant - no special styling, shows value with indicator
|
|
334
345
|
if (variant === "text") {
|
|
@@ -347,23 +358,30 @@ export const CitationComponent = forwardRef(({ citation, children, className, di
|
|
|
347
358
|
};
|
|
348
359
|
// Determine if popover should be shown
|
|
349
360
|
const isPopoverHidden = popoverPosition === "hidden";
|
|
350
|
-
const shouldShowPopover = !isPopoverHidden &&
|
|
361
|
+
const shouldShowPopover = !isPopoverHidden &&
|
|
362
|
+
verification &&
|
|
363
|
+
(verification.verificationImageBase64 || verification.matchSnippet);
|
|
351
364
|
// Determine if status tooltip should be shown (miss/partial without full verification)
|
|
352
365
|
const shouldShowStatusTooltip = !isPopoverHidden && (isMiss || isPartialMatch) && !shouldShowPopover;
|
|
353
366
|
// Popover content - determine position class (only "top" or "bottom" add classes)
|
|
354
367
|
const popoverPositionClass = popoverPosition === "bottom" ? "dc-popover--bottom" : "";
|
|
355
|
-
const popoverContent = shouldShowPopover ? (_jsx("span", { className: classNames("dc-popover", popoverPositionClass), children: renderPopoverContent ? (renderPopoverContent({
|
|
368
|
+
const popoverContent = shouldShowPopover ? (_jsx("span", { className: classNames("dc-popover", popoverPositionClass), children: renderPopoverContent ? (renderPopoverContent({
|
|
369
|
+
citation,
|
|
370
|
+
verification: verification ?? null,
|
|
371
|
+
status,
|
|
372
|
+
})) : (_jsx(DefaultPopoverContent, { citation: citation, verification: verification ?? null, status: status, onImageClick: handleImageClick })) })) : null;
|
|
356
373
|
// Status tooltip for miss/partial explanations
|
|
357
|
-
const statusTooltipContent = shouldShowStatusTooltip ? (_jsx(StatusTooltipContent, { citation: citation, status: status,
|
|
358
|
-
const citationTrigger = (_jsx("span", { ref: node => {
|
|
359
|
-
containerRef.current =
|
|
374
|
+
const statusTooltipContent = shouldShowStatusTooltip ? (_jsx(StatusTooltipContent, { citation: citation, status: status, verification: verification ?? null, isExpanded: isPhrasesExpanded, onToggleExpand: handleTogglePhrases })) : null;
|
|
375
|
+
const citationTrigger = (_jsx("span", { ref: (node) => {
|
|
376
|
+
containerRef.current =
|
|
377
|
+
node;
|
|
360
378
|
if (typeof ref === "function") {
|
|
361
379
|
ref(node);
|
|
362
380
|
}
|
|
363
381
|
else if (ref) {
|
|
364
382
|
ref.current = node;
|
|
365
383
|
}
|
|
366
|
-
}, "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() }));
|
|
367
385
|
// Image overlay for full-size view
|
|
368
386
|
const imageOverlay = expandedImageSrc ? (_jsx(ImageOverlay, { src: expandedImageSrc, alt: "Citation verification - full size", onClose: handleCloseOverlay })) : null;
|
|
369
387
|
// Wrap with popover or status tooltip if needed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { type ReactNode } from "react";
|
|
2
2
|
import type { CitationStatus } from "../types/citation.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { Verification } from "../types/verification.js";
|
|
4
4
|
import type { SearchState } from "../types/search.js";
|
|
5
5
|
import type { BaseCitationProps, CitationVariant as CitationVariantType, CitationEventHandlers } from "./types.js";
|
|
6
6
|
/**
|
|
@@ -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
|
*/
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { memo, useMemo, useCallback, forwardRef } from "react";
|
|
2
|
+
import { memo, useMemo, useCallback, forwardRef, } from "react";
|
|
3
3
|
import { getCitationStatus } from "../parsing/parseCitation.js";
|
|
4
4
|
import { generateCitationKey, generateCitationInstanceId, getCitationDisplayText, getCitationValueText, classNames, } from "./utils.js";
|
|
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,
|
|
@@ -67,7 +67,8 @@ export const ChipCitation = forwardRef(({ citation, children, className, display
|
|
|
67
67
|
: isPending
|
|
68
68
|
? "citation-chip--pending"
|
|
69
69
|
: "";
|
|
70
|
-
return (_jsxs(_Fragment, { children: [children, _jsxs("span", { ref: ref, "data-citation-id": citationKey, "data-citation-instance": citationInstanceId, "data-variant": "chip", className: classNames("citation-chip", sizeClasses[size], statusClass, className), onMouseEnter: preventTooltips ? undefined : handleMouseEnter, onMouseLeave: preventTooltips ? undefined : handleMouseLeave, onMouseDown: handleClick, onClick: e => e.stopPropagation(), "aria-label": displayText ? `Citation: ${displayText}` : undefined, children: [showIcon &&
|
|
70
|
+
return (_jsxs(_Fragment, { children: [children, _jsxs("span", { ref: ref, "data-citation-id": citationKey, "data-citation-instance": citationInstanceId, "data-variant": "chip", className: classNames("citation-chip", sizeClasses[size], statusClass, className), onMouseEnter: preventTooltips ? undefined : handleMouseEnter, onMouseLeave: preventTooltips ? undefined : handleMouseLeave, onMouseDown: handleClick, onClick: (e) => e.stopPropagation(), "aria-label": displayText ? `Citation: ${displayText}` : undefined, children: [showIcon &&
|
|
71
|
+
(icon || _jsx("span", { className: "citation-chip__icon", children: "\uD83D\uDCC4" })), valueText && !displayCitationValue && (_jsx("span", { className: "citation-chip__value", children: valueText })), _jsx("span", { className: "citation-chip__text", children: displayText }), isPartialMatch && renderPartialIndicator(status), isVerified && !isPartialMatch && renderVerifiedIndicator(status), isPending && (_jsx("span", { className: "citation-chip__pending", children: pendingContent }))] })] }));
|
|
71
72
|
});
|
|
72
73
|
ChipCitation.displayName = "ChipCitation";
|
|
73
74
|
/**
|
|
@@ -76,12 +77,12 @@ ChipCitation.displayName = "ChipCitation";
|
|
|
76
77
|
*
|
|
77
78
|
* @example
|
|
78
79
|
* ```tsx
|
|
79
|
-
* <SuperscriptCitation citation={citation}
|
|
80
|
+
* <SuperscriptCitation citation={citation} verification={found} />
|
|
80
81
|
* // Renders: Text content¹
|
|
81
82
|
* ```
|
|
82
83
|
*/
|
|
83
|
-
export const SuperscriptCitation = forwardRef(({ citation, children, className, displayCitationValue = false, fallbackDisplay,
|
|
84
|
-
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);
|
|
85
86
|
const { isVerified, isMiss, isPartialMatch, isPending } = status;
|
|
86
87
|
const displayText = useMemo(() => getCitationDisplayText(citation, {
|
|
87
88
|
displayCitationValue,
|
|
@@ -108,7 +109,7 @@ export const SuperscriptCitation = forwardRef(({ citation, children, className,
|
|
|
108
109
|
: isPending
|
|
109
110
|
? "citation-superscript--pending"
|
|
110
111
|
: "";
|
|
111
|
-
return (_jsxs(_Fragment, { children: [children, _jsxs("sup", { ref: ref, "data-citation-id": citationKey, "data-citation-instance": citationInstanceId, "data-variant": "superscript", className: classNames("citation-superscript", statusClass, className), onMouseEnter: preventTooltips ? undefined : handleMouseEnter, onMouseLeave: preventTooltips ? undefined : handleMouseLeave, onMouseDown: handleClick, onClick: e => e.stopPropagation(), "aria-label": `Citation ${displayText}`, children: [showBrackets && "[", displayText, isPartialMatch && renderPartialIndicator(status), isVerified && !isPartialMatch && renderVerifiedIndicator(status), isPending && pendingContent, showBrackets && "]"] })] }));
|
|
112
|
+
return (_jsxs(_Fragment, { children: [children, _jsxs("sup", { ref: ref, "data-citation-id": citationKey, "data-citation-instance": citationInstanceId, "data-variant": "superscript", className: classNames("citation-superscript", statusClass, className), onMouseEnter: preventTooltips ? undefined : handleMouseEnter, onMouseLeave: preventTooltips ? undefined : handleMouseLeave, onMouseDown: handleClick, onClick: (e) => e.stopPropagation(), "aria-label": `Citation ${displayText}`, children: [showBrackets && "[", displayText, isPartialMatch && renderPartialIndicator(status), isVerified && !isPartialMatch && renderVerifiedIndicator(status), isPending && pendingContent, showBrackets && "]"] })] }));
|
|
112
113
|
});
|
|
113
114
|
SuperscriptCitation.displayName = "SuperscriptCitation";
|
|
114
115
|
const FOOTNOTE_SYMBOLS = ["*", "†", "‡", "§", "‖", "¶"];
|
|
@@ -122,8 +123,8 @@ const FOOTNOTE_SYMBOLS = ["*", "†", "‡", "§", "‖", "¶"];
|
|
|
122
123
|
* // Renders: Text content*
|
|
123
124
|
* ```
|
|
124
125
|
*/
|
|
125
|
-
export const FootnoteCitation = forwardRef(({ citation, children, className, fallbackDisplay,
|
|
126
|
-
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);
|
|
127
128
|
const { isVerified, isMiss, isPartialMatch, isPending } = status;
|
|
128
129
|
const displaySymbol = useMemo(() => {
|
|
129
130
|
if (symbolStyle === "custom" && customSymbol)
|
|
@@ -159,7 +160,7 @@ export const FootnoteCitation = forwardRef(({ citation, children, className, fal
|
|
|
159
160
|
: isPending
|
|
160
161
|
? "citation-footnote--pending"
|
|
161
162
|
: "";
|
|
162
|
-
return (_jsxs(_Fragment, { children: [children, _jsxs("sup", { ref: ref, "data-citation-id": citationKey, "data-citation-instance": citationInstanceId, "data-variant": "footnote", className: classNames("citation-footnote", statusClass, className), onMouseEnter: preventTooltips ? undefined : handleMouseEnter, onMouseLeave: preventTooltips ? undefined : handleMouseLeave, onMouseDown: handleClick, onClick: e => e.stopPropagation(), "aria-label": `Footnote ${displaySymbol}`, children: [displaySymbol, isPartialMatch && renderPartialIndicator(status), isVerified && !isPartialMatch && renderVerifiedIndicator(status), isPending && pendingContent] })] }));
|
|
163
|
+
return (_jsxs(_Fragment, { children: [children, _jsxs("sup", { ref: ref, "data-citation-id": citationKey, "data-citation-instance": citationInstanceId, "data-variant": "footnote", className: classNames("citation-footnote", statusClass, className), onMouseEnter: preventTooltips ? undefined : handleMouseEnter, onMouseLeave: preventTooltips ? undefined : handleMouseLeave, onMouseDown: handleClick, onClick: (e) => e.stopPropagation(), "aria-label": `Footnote ${displaySymbol}`, children: [displaySymbol, isPartialMatch && renderPartialIndicator(status), isVerified && !isPartialMatch && renderVerifiedIndicator(status), isPending && pendingContent] })] }));
|
|
163
164
|
});
|
|
164
165
|
FootnoteCitation.displayName = "FootnoteCitation";
|
|
165
166
|
/**
|
|
@@ -173,8 +174,8 @@ FootnoteCitation.displayName = "FootnoteCitation";
|
|
|
173
174
|
* ```
|
|
174
175
|
*/
|
|
175
176
|
export const InlineCitation = forwardRef(({ citation, children, className, displayCitationValue = true, // Default to merged for inline
|
|
176
|
-
fallbackDisplay,
|
|
177
|
-
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);
|
|
178
179
|
const { isVerified, isMiss, isPartialMatch, isPending } = status;
|
|
179
180
|
const displayText = useMemo(() => getCitationDisplayText(citation, {
|
|
180
181
|
displayCitationValue,
|
|
@@ -202,7 +203,7 @@ fallbackDisplay, foundCitation, eventHandlers, preventTooltips = false, pendingC
|
|
|
202
203
|
? "citation-inline--pending"
|
|
203
204
|
: "";
|
|
204
205
|
const underlineClass = `citation-inline--underline-${underlineStyle}`;
|
|
205
|
-
return (_jsxs(_Fragment, { children: [children, _jsxs("span", { ref: ref, "data-citation-id": citationKey, "data-citation-instance": citationInstanceId, "data-variant": "inline", className: classNames("citation-inline", underlineClass, statusClass, className), onMouseEnter: preventTooltips ? undefined : handleMouseEnter, onMouseLeave: preventTooltips ? undefined : handleMouseLeave, onMouseDown: handleClick, onClick: e => e.stopPropagation(), "aria-label": `Citation: ${displayText}`, children: [displayText, isPartialMatch && renderPartialIndicator(status), isVerified && !isPartialMatch && renderVerifiedIndicator(status), isPending && _jsx("span", { className: "citation-inline__pending", children: pendingContent })] })] }));
|
|
206
|
+
return (_jsxs(_Fragment, { children: [children, _jsxs("span", { ref: ref, "data-citation-id": citationKey, "data-citation-instance": citationInstanceId, "data-variant": "inline", className: classNames("citation-inline", underlineClass, statusClass, className), onMouseEnter: preventTooltips ? undefined : handleMouseEnter, onMouseLeave: preventTooltips ? undefined : handleMouseLeave, onMouseDown: handleClick, onClick: (e) => e.stopPropagation(), "aria-label": `Citation: ${displayText}`, children: [displayText, isPartialMatch && renderPartialIndicator(status), isVerified && !isPartialMatch && renderVerifiedIndicator(status), isPending && (_jsx("span", { className: "citation-inline__pending", children: pendingContent }))] })] }));
|
|
206
207
|
});
|
|
207
208
|
InlineCitation.displayName = "InlineCitation";
|
|
208
209
|
/**
|
|
@@ -215,8 +216,8 @@ InlineCitation.displayName = "InlineCitation";
|
|
|
215
216
|
* // Renders: 1
|
|
216
217
|
* ```
|
|
217
218
|
*/
|
|
218
|
-
export const MinimalCitation = forwardRef(({ citation, children, className, displayCitationValue = false, fallbackDisplay,
|
|
219
|
-
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);
|
|
220
221
|
const { isVerified, isMiss, isPartialMatch, isPending } = status;
|
|
221
222
|
const displayText = useMemo(() => getCitationDisplayText(citation, {
|
|
222
223
|
displayCitationValue,
|
|
@@ -243,7 +244,7 @@ export const MinimalCitation = forwardRef(({ citation, children, className, disp
|
|
|
243
244
|
: isPending
|
|
244
245
|
? "citation-minimal--pending"
|
|
245
246
|
: "";
|
|
246
|
-
return (_jsxs(_Fragment, { children: [children, _jsxs("span", { ref: ref, "data-citation-id": citationKey, "data-citation-instance": citationInstanceId, "data-variant": "minimal", className: classNames("citation-minimal", statusClass, className), onMouseEnter: preventTooltips ? undefined : handleMouseEnter, onMouseLeave: preventTooltips ? undefined : handleMouseLeave, onMouseDown: handleClick, onClick: e => e.stopPropagation(), "aria-label": `Citation ${displayText}`, children: [displayText, showStatusIndicator && (_jsxs(_Fragment, { children: [isPartialMatch && renderPartialIndicator(status), isVerified && !isPartialMatch && renderVerifiedIndicator(status), isPending && pendingContent] }))] })] }));
|
|
247
|
+
return (_jsxs(_Fragment, { children: [children, _jsxs("span", { ref: ref, "data-citation-id": citationKey, "data-citation-instance": citationInstanceId, "data-variant": "minimal", className: classNames("citation-minimal", statusClass, className), onMouseEnter: preventTooltips ? undefined : handleMouseEnter, onMouseLeave: preventTooltips ? undefined : handleMouseLeave, onMouseDown: handleClick, onClick: (e) => e.stopPropagation(), "aria-label": `Citation ${displayText}`, children: [displayText, showStatusIndicator && (_jsxs(_Fragment, { children: [isPartialMatch && renderPartialIndicator(status), isVerified && !isPartialMatch && renderVerifiedIndicator(status), isPending && pendingContent] }))] })] }));
|
|
247
248
|
});
|
|
248
249
|
MinimalCitation.displayName = "MinimalCitation";
|
|
249
250
|
/**
|
|
@@ -259,7 +260,7 @@ export const CitationVariantFactory = forwardRef(({ variant = "bracket", chipPro
|
|
|
259
260
|
case "chip":
|
|
260
261
|
return _jsx(ChipCitation, { ref: ref, ...props, ...chipProps });
|
|
261
262
|
case "superscript":
|
|
262
|
-
return _jsx(SuperscriptCitation, { ref: ref, ...props, ...superscriptProps });
|
|
263
|
+
return (_jsx(SuperscriptCitation, { ref: ref, ...props, ...superscriptProps }));
|
|
263
264
|
case "footnote":
|
|
264
265
|
return _jsx(FootnoteCitation, { ref: ref, ...props, ...footnoteProps });
|
|
265
266
|
case "inline":
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import React, { type ReactNode, type HTMLAttributes, type MouseEvent, type TouchEvent } from "react";
|
|
6
6
|
import type { Citation as CitationType, CitationStatus } from "../types/citation.js";
|
|
7
|
-
import type {
|
|
7
|
+
import type { Verification } from "../types/verification.js";
|
|
8
8
|
import type { SearchState } from "../types/search.js";
|
|
9
9
|
interface CitationContextValue {
|
|
10
10
|
citation: CitationType;
|
|
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
|
@@ -5,7 +5,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
5
5
|
*/
|
|
6
6
|
import { createContext, useContext, useMemo, useCallback, forwardRef, } from "react";
|
|
7
7
|
import { getCitationStatus } from "../parsing/parseCitation.js";
|
|
8
|
-
import { generateCitationKey, generateCitationInstanceId, classNames } from "./utils.js";
|
|
8
|
+
import { generateCitationKey, generateCitationInstanceId, classNames, } from "./utils.js";
|
|
9
9
|
const CitationContext = createContext(null);
|
|
10
10
|
/** Access citation context. Must be used within Citation.Root. */
|
|
11
11
|
export function useCitationContext() {
|
|
@@ -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,
|
|
@@ -83,7 +83,9 @@ export const CitationTrigger = forwardRef(({ children, className, onCitationClic
|
|
|
83
83
|
onCitationTouchEnd?.(citation, citationKey, e);
|
|
84
84
|
}
|
|
85
85
|
}, [onTouchEnd, isMobile, onCitationTouchEnd, citation, citationKey]);
|
|
86
|
-
const statusClasses = classNames(status.isVerified &&
|
|
86
|
+
const statusClasses = classNames(status.isVerified &&
|
|
87
|
+
!status.isPartialMatch &&
|
|
88
|
+
"citation-trigger--verified", status.isPartialMatch && "citation-trigger--partial", status.isMiss && "citation-trigger--miss", status.isPending && "citation-trigger--pending");
|
|
87
89
|
return (_jsx("span", { ref: ref, role: "button", tabIndex: 0, className: classNames("citation-trigger", statusClasses, className), onClick: handleClick, onMouseDown: handleMouseDown, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onTouchEndCapture: isMobile ? handleTouchEnd : undefined, ...props, children: children }));
|
|
88
90
|
});
|
|
89
91
|
CitationTrigger.displayName = "Citation.Trigger";
|
|
@@ -99,7 +101,10 @@ export const CitationNumber = forwardRef(({ className, number, ...props }, ref)
|
|
|
99
101
|
if (number !== undefined)
|
|
100
102
|
return String(number);
|
|
101
103
|
if (config.displayCitationValue) {
|
|
102
|
-
return citation.value ||
|
|
104
|
+
return (citation.value ||
|
|
105
|
+
citation.citationNumber?.toString() ||
|
|
106
|
+
config.fallbackDisplay ||
|
|
107
|
+
"");
|
|
103
108
|
}
|
|
104
109
|
return citation.citationNumber?.toString() || "";
|
|
105
110
|
}, [number, citation, config]);
|
package/lib/react/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Citation, CitationStatus } from "../types/citation.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { Verification } from "../types/verification.js";
|
|
3
3
|
import type { SearchState } from "../types/search.js";
|
|
4
4
|
/**
|
|
5
5
|
* Available citation display variants.
|
|
@@ -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 */
|
|
@@ -186,7 +186,7 @@ export interface CitationEventHandlers {
|
|
|
186
186
|
export interface CitationTooltipProps {
|
|
187
187
|
children: React.ReactNode;
|
|
188
188
|
citation: Citation;
|
|
189
|
-
|
|
189
|
+
verification?: Verification | null;
|
|
190
190
|
searchState?: SearchState | null;
|
|
191
191
|
shouldShowTooltip: boolean;
|
|
192
192
|
}
|
package/lib/types/citation.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { type ScreenBox } from "./boxes.js";
|
|
2
|
-
import { type
|
|
2
|
+
import { type Verification } from "./verification.js";
|
|
3
3
|
export type OutputImageFormat = "jpeg" | "png" | "avif" | undefined | null;
|
|
4
4
|
export declare const DEFAULT_OUTPUT_IMAGE_FORMAT: "avif";
|
|
5
5
|
export interface VerifyCitationResponse {
|
|
6
|
-
|
|
7
|
-
[key: string]:
|
|
6
|
+
verifications: {
|
|
7
|
+
[key: string]: Verification;
|
|
8
8
|
};
|
|
9
9
|
}
|
|
10
10
|
export interface VerifyCitationRequest {
|
|
@@ -21,11 +21,11 @@ export interface Citation {
|
|
|
21
21
|
keySpan?: string | null;
|
|
22
22
|
value?: string | null;
|
|
23
23
|
startPageKey?: string | null;
|
|
24
|
-
pageNumber?: number | null;
|
|
25
24
|
lineIds?: number[] | null;
|
|
26
25
|
reasoning?: string | null;
|
|
27
26
|
selection?: ScreenBox | null;
|
|
28
27
|
citationNumber?: number;
|
|
28
|
+
pageNumber?: number | null;
|
|
29
29
|
timestamps?: {
|
|
30
30
|
endTime?: string;
|
|
31
31
|
startTime?: string;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export type { Citation, CitationStatus, VerifyCitationRequest, VerifyCitationResponse, OutputImageFormat, } from "./citation.js";
|
|
7
7
|
export { DEFAULT_OUTPUT_IMAGE_FORMAT } from "./citation.js";
|
|
8
|
-
export type {
|
|
9
|
-
export {
|
|
8
|
+
export type { Verification } from "./verification.js";
|
|
9
|
+
export { NOT_FOUND_VERIFICATION_INDEX, PENDING_VERIFICATION_INDEX, BLANK_VERIFICATION, deterministicIdFromVerification, } from "./verification.js";
|
|
10
10
|
export type { SearchState, SearchStatus } from "./search.js";
|
|
11
11
|
export type { ScreenBox, PdfSpaceItem, IVertex } from "./boxes.js";
|
package/lib/types/index.js
CHANGED
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
* @packageDocumentation
|
|
5
5
|
*/
|
|
6
6
|
export { DEFAULT_OUTPUT_IMAGE_FORMAT } from "./citation.js";
|
|
7
|
-
export {
|
|
7
|
+
export { NOT_FOUND_VERIFICATION_INDEX, PENDING_VERIFICATION_INDEX, BLANK_VERIFICATION, deterministicIdFromVerification, } from "./verification.js";
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { type Citation } from "./citation.js";
|
|
2
2
|
import { type SearchState } from "./search.js";
|
|
3
3
|
import { type PdfSpaceItem } from "./boxes.js";
|
|
4
|
-
export declare const
|
|
5
|
-
export declare const
|
|
6
|
-
export declare const
|
|
7
|
-
export declare function
|
|
8
|
-
export interface
|
|
4
|
+
export declare const NOT_FOUND_VERIFICATION_INDEX = -1;
|
|
5
|
+
export declare const PENDING_VERIFICATION_INDEX = -2;
|
|
6
|
+
export declare const BLANK_VERIFICATION: Verification;
|
|
7
|
+
export declare function deterministicIdFromVerification(verification: Verification): string;
|
|
8
|
+
export interface Verification {
|
|
9
9
|
regex?: RegExp | null;
|
|
10
10
|
lowerCaseSearchTerm: string | null;
|
|
11
11
|
label?: string | null;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { sha1Hash } from "../utils/sha.js";
|
|
2
|
+
export const NOT_FOUND_VERIFICATION_INDEX = -1;
|
|
3
|
+
export const PENDING_VERIFICATION_INDEX = -2;
|
|
4
|
+
export const BLANK_VERIFICATION = {
|
|
5
|
+
pageNumber: NOT_FOUND_VERIFICATION_INDEX,
|
|
6
|
+
regex: null,
|
|
7
|
+
lowerCaseSearchTerm: null,
|
|
8
|
+
attachmentId: null,
|
|
9
|
+
matchSnippet: null,
|
|
10
|
+
source: null,
|
|
11
|
+
citation: {
|
|
12
|
+
fileId: undefined,
|
|
13
|
+
startPageKey: null,
|
|
14
|
+
fullPhrase: null,
|
|
15
|
+
keySpan: null,
|
|
16
|
+
lineIds: null,
|
|
17
|
+
reasoning: null,
|
|
18
|
+
pageNumber: NOT_FOUND_VERIFICATION_INDEX,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
export function deterministicIdFromVerification(verification) {
|
|
22
|
+
return sha1Hash(`${verification.lowerCaseSearchTerm}-${verification.attachmentId}-${verification.pageNumber}-${verification.hitIndexWithinPage}-${verification.matchSnippet}-${verification?.hitIndexWithinPage}`);
|
|
23
|
+
}
|
package/package.json
CHANGED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { sha1Hash } from "../utils/sha.js";
|
|
2
|
-
export const NOT_FOUND_HIGHLIGHT_INDEX = -1;
|
|
3
|
-
export const PENDING_HIGHLIGHT_INDEX = -2;
|
|
4
|
-
export const BLANK_HIGHLIGHT_LOCATION = {
|
|
5
|
-
pageNumber: NOT_FOUND_HIGHLIGHT_INDEX,
|
|
6
|
-
regex: null,
|
|
7
|
-
lowerCaseSearchTerm: null,
|
|
8
|
-
attachmentId: null,
|
|
9
|
-
matchSnippet: null,
|
|
10
|
-
source: null,
|
|
11
|
-
citation: {
|
|
12
|
-
startPageKey: null,
|
|
13
|
-
lineIds: null,
|
|
14
|
-
pageNumber: NOT_FOUND_HIGHLIGHT_INDEX,
|
|
15
|
-
fileId: undefined,
|
|
16
|
-
fullPhrase: null,
|
|
17
|
-
value: null,
|
|
18
|
-
},
|
|
19
|
-
};
|
|
20
|
-
export function deterministicIdFromHighlightLocation(highlightLocation) {
|
|
21
|
-
return sha1Hash(`${highlightLocation.lowerCaseSearchTerm}-${highlightLocation.attachmentId}-${highlightLocation.pageNumber}-${highlightLocation.hitIndexWithinPage}-${highlightLocation.matchSnippet}-${highlightLocation?.hitIndexWithinPage}`);
|
|
22
|
-
}
|