@deepcitation/deepcitation-js 1.1.24 → 1.1.25

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.
@@ -1,16 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { forwardRef, memo, useCallback, useEffect, useMemo, useState, } from "react";
3
3
  import { createPortal } from "react-dom";
4
- import { CheckIcon, WarningIcon } from "./icons.js";
4
+ import { CheckIcon, SpinnerIcon, WarningIcon } from "./icons.js";
5
5
  import { Popover, PopoverContent, PopoverTrigger } from "./Popover.js";
6
- import { generateCitationInstanceId, generateCitationKey, getCitationDisplayText, } from "./utils.js";
6
+ import { cn, generateCitationInstanceId, generateCitationKey, getCitationDisplayText, } from "./utils.js";
7
7
  import { useSmartDiff } from "./useSmartDiff.js";
8
- // =============================================================================
9
- // UTILITY FUNCTIONS
10
- // =============================================================================
11
- function cn(...classes) {
12
- return classes.filter(Boolean).join(" ");
13
- }
14
8
  function getStatusLabel(status) {
15
9
  if (status.isVerified && !status.isPartialMatch)
16
10
  return "Verified";
@@ -30,7 +24,12 @@ function getStatusFromVerification(verification) {
30
24
  const status = verification?.status;
31
25
  // No verification or no status = pending
32
26
  if (!verification || !status) {
33
- return { isVerified: false, isMiss: false, isPartialMatch: false, isPending: true };
27
+ return {
28
+ isVerified: false,
29
+ isMiss: false,
30
+ isPartialMatch: false,
31
+ isPending: true,
32
+ };
34
33
  }
35
34
  const isMiss = status === "not_found";
36
35
  const isPending = status === "pending" || status === "loading";
@@ -74,14 +73,12 @@ function ImageOverlay({ src, alt, onClose }) {
74
73
  //
75
74
  // Use `renderIndicator` prop to customize. Use `variant="indicator"` to show only the icon.
76
75
  // =============================================================================
77
- /** Spinner component for loading/pending state */
78
- const Spinner = ({ className }) => (_jsxs("svg", { className: cn("animate-spin", className), xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", width: "12", height: "12", children: [_jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), _jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] }));
79
76
  /** Verified indicator - green checkmark for exact matches */
80
77
  const VerifiedIndicator = () => (_jsx("span", { className: "inline-flex relative ml-0.5 text-green-600 dark:text-green-500", "aria-hidden": "true", children: _jsx(CheckIcon, {}) }));
81
78
  /** Partial match indicator - amber checkmark for partial/relocated matches */
82
79
  const PartialIndicator = () => (_jsx("span", { className: "inline-flex relative ml-0.5 text-amber-600 dark:text-amber-500", "aria-hidden": "true", children: _jsx(CheckIcon, {}) }));
83
80
  /** Pending indicator - spinner for loading state */
84
- const PendingIndicator = () => (_jsx("span", { className: "inline-flex ml-1 text-gray-400 dark:text-gray-500", "aria-hidden": "true", children: _jsx(Spinner, {}) }));
81
+ const PendingIndicator = () => (_jsx("span", { className: "inline-flex ml-1 text-gray-400 dark:text-gray-500", "aria-hidden": "true", children: _jsx(SpinnerIcon, {}) }));
85
82
  /** Miss indicator - red warning triangle for not found */
86
83
  const MissIndicator = () => (_jsx("span", { className: "inline-flex relative ml-0.5 text-red-500 dark:text-red-400", "aria-hidden": "true", children: _jsx(WarningIcon, {}) }));
87
84
  function DefaultPopoverContent({ citation, verification, status, onImageClick, }) {
@@ -101,7 +98,9 @@ function DefaultPopoverContent({ citation, verification, status, onImageClick, }
101
98
  const pageNumber = verification?.verifiedPageNumber;
102
99
  if (!hasSnippet && !statusLabel)
103
100
  return null;
104
- return (_jsxs("div", { className: "p-3 flex flex-col gap-2 min-w-[200px] max-w-[400px]", children: [statusLabel && (_jsx("span", { className: cn("text-xs font-medium", status.isVerified && !status.isPartialMatch && "text-green-600 dark:text-green-500", status.isPartialMatch && "text-amber-600 dark:text-amber-500", status.isMiss && "text-red-600 dark:text-red-500", status.isPending && "text-gray-500 dark:text-gray-400"), children: statusLabel })), hasSnippet && (_jsxs("span", { className: "text-sm text-gray-700 dark:text-gray-300 italic", children: ["\"", verification.verifiedMatchSnippet, "\""] })), pageNumber && pageNumber > 0 && (_jsxs("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: ["Page ", pageNumber] })), (isMiss || isPartialMatch) && (_jsx(DiffDetails, { citation: citation, verification: verification, status: status }))] }));
101
+ return (_jsxs("div", { className: "p-3 flex flex-col gap-2 min-w-[200px] max-w-[400px]", children: [statusLabel && (_jsx("span", { className: cn("text-xs font-medium", status.isVerified &&
102
+ !status.isPartialMatch &&
103
+ "text-green-600 dark:text-green-500", status.isPartialMatch && "text-amber-600 dark:text-amber-500", status.isMiss && "text-red-600 dark:text-red-500", status.isPending && "text-gray-500 dark:text-gray-400"), children: statusLabel })), hasSnippet && (_jsxs("span", { className: "text-sm text-gray-700 dark:text-gray-300 italic", children: ["\"", verification.verifiedMatchSnippet, "\""] })), pageNumber && pageNumber > 0 && (_jsxs("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: ["Page ", pageNumber] })), (isMiss || isPartialMatch) && (_jsx(DiffDetails, { citation: citation, verification: verification, status: status }))] }));
105
104
  }
106
105
  // =============================================================================
107
106
  // DIFF DETAILS COMPONENT
@@ -128,10 +127,16 @@ function DiffDetails({ citation, verification, status, }) {
128
127
  const pageDiffers = expectedPage != null && actualPage != null && expectedPage !== actualPage;
129
128
  // For "not_found" status, show expected text and "Not found" message
130
129
  if (isMiss) {
131
- return (_jsxs("div", { className: "mt-2 pt-2 border-t border-gray-200 dark:border-gray-700 text-xs space-y-2", children: [expectedText && (_jsxs("div", { children: [_jsx("span", { className: "text-gray-500 dark:text-gray-400 font-medium uppercase text-[10px]", children: "Expected" }), _jsx("p", { className: "mt-1 p-2 bg-gray-100 dark:bg-gray-800 rounded font-mono text-[11px] break-words text-red-600 dark:text-red-400 line-through opacity-70", children: expectedText.length > 100 ? expectedText.slice(0, 100) + "…" : expectedText })] })), _jsxs("div", { children: [_jsx("span", { className: "text-gray-500 dark:text-gray-400 font-medium uppercase text-[10px]", children: "Found" }), _jsx("p", { className: "mt-1 p-2 bg-gray-100 dark:bg-gray-800 rounded font-mono text-[11px] text-amber-600 dark:text-amber-500 italic", children: "Not found in source" })] })] }));
130
+ return (_jsxs("div", { className: "mt-2 pt-2 border-t border-gray-200 dark:border-gray-700 text-xs space-y-2", children: [expectedText && (_jsxs("div", { children: [_jsx("span", { className: "text-gray-500 dark:text-gray-400 font-medium uppercase text-[10px]", children: "Expected" }), _jsx("p", { className: "mt-1 p-2 bg-gray-100 dark:bg-gray-800 rounded font-mono text-[11px] break-words text-red-600 dark:text-red-400 line-through opacity-70", children: expectedText.length > 100
131
+ ? expectedText.slice(0, 100) + "…"
132
+ : expectedText })] })), _jsxs("div", { children: [_jsx("span", { className: "text-gray-500 dark:text-gray-400 font-medium uppercase text-[10px]", children: "Found" }), _jsx("p", { className: "mt-1 p-2 bg-gray-100 dark:bg-gray-800 rounded font-mono text-[11px] text-amber-600 dark:text-amber-500 italic", children: "Not found in source" })] })] }));
132
133
  }
133
134
  // For partial matches, show word-level diff
134
- return (_jsxs("div", { className: "mt-2 pt-2 border-t border-gray-200 dark:border-gray-700 text-xs space-y-2", children: [expectedText && actualText && hasDiff ? (_jsxs("div", { children: [_jsx("span", { className: "text-gray-500 dark:text-gray-400 font-medium uppercase text-[10px]", children: "Diff" }), _jsx("div", { className: "mt-1 p-2 bg-gray-100 dark:bg-gray-800 rounded font-mono text-[11px] break-words text-gray-700 dark:text-gray-300", children: isHighVariance ? (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { children: [_jsx("span", { className: "text-gray-500 dark:text-gray-400 text-[10px]", children: "Expected: " }), _jsx("span", { className: "text-red-600 dark:text-red-400 line-through opacity-70", children: expectedText.length > 100 ? expectedText.slice(0, 100) + "…" : expectedText })] }), _jsxs("div", { children: [_jsx("span", { className: "text-gray-500 dark:text-gray-400 text-[10px]", children: "Found: " }), _jsx("span", { className: "text-green-600 dark:text-green-400", children: actualText.length > 100 ? actualText.slice(0, 100) + "…" : actualText })] })] })) : (
135
+ return (_jsxs("div", { className: "mt-2 pt-2 border-t border-gray-200 dark:border-gray-700 text-xs space-y-2", children: [expectedText && actualText && hasDiff ? (_jsxs("div", { children: [_jsx("span", { className: "text-gray-500 dark:text-gray-400 font-medium uppercase text-[10px]", children: "Diff" }), _jsx("div", { className: "mt-1 p-2 bg-gray-100 dark:bg-gray-800 rounded font-mono text-[11px] break-words text-gray-700 dark:text-gray-300", children: isHighVariance ? (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { children: [_jsxs("span", { className: "text-gray-500 dark:text-gray-400 text-[10px]", children: ["Expected:", " "] }), _jsx("span", { className: "text-red-600 dark:text-red-400 line-through opacity-70", children: expectedText.length > 100
136
+ ? expectedText.slice(0, 100) + "…"
137
+ : expectedText })] }), _jsxs("div", { children: [_jsxs("span", { className: "text-gray-500 dark:text-gray-400 text-[10px]", children: ["Found:", " "] }), _jsx("span", { className: "text-green-600 dark:text-green-400", children: actualText.length > 100
138
+ ? actualText.slice(0, 100) + "…"
139
+ : actualText })] })] })) : (
135
140
  // Inline word-level diff
136
141
  diffResult.map((block, blockIndex) => (_jsx("span", { children: block.parts.map((part, partIndex) => {
137
142
  const key = `p-${blockIndex}-${partIndex}`;
@@ -145,7 +150,9 @@ function DiffDetails({ citation, verification, status, }) {
145
150
  return _jsx("span", { children: part.value }, key);
146
151
  }) }, `block-${blockIndex}`)))) })] })) : expectedText && !hasDiff ? (
147
152
  // Text matches exactly (partial match is due to location difference)
148
- _jsxs("div", { children: [_jsx("span", { className: "text-gray-500 dark:text-gray-400 font-medium uppercase text-[10px]", children: "Text" }), _jsx("p", { className: "mt-1 p-2 bg-gray-100 dark:bg-gray-800 rounded font-mono text-[11px] break-words text-gray-700 dark:text-gray-300", children: expectedText.length > 100 ? expectedText.slice(0, 100) + "…" : expectedText })] })) : null, pageDiffers && (_jsxs("div", { children: [_jsx("span", { className: "text-gray-500 dark:text-gray-400 font-medium uppercase text-[10px]", children: "Page" }), _jsxs("p", { className: "mt-1 font-mono text-[11px] text-gray-700 dark:text-gray-300", children: [_jsx("span", { className: "text-red-600 dark:text-red-400 line-through opacity-70", children: expectedPage }), " → ", actualPage] })] })), lineIdDiffers && (_jsxs("div", { children: [_jsx("span", { className: "text-gray-500 dark:text-gray-400 font-medium uppercase text-[10px]", children: "Line" }), _jsxs("p", { className: "mt-1 font-mono text-[11px] text-gray-700 dark:text-gray-300", children: [_jsx("span", { className: "text-red-600 dark:text-red-400 line-through opacity-70", children: expectedLineIds?.join(", ") }), " → ", actualLineIds?.join(", ")] })] }))] }));
153
+ _jsxs("div", { children: [_jsx("span", { className: "text-gray-500 dark:text-gray-400 font-medium uppercase text-[10px]", children: "Text" }), _jsx("p", { className: "mt-1 p-2 bg-gray-100 dark:bg-gray-800 rounded font-mono text-[11px] break-words text-gray-700 dark:text-gray-300", children: expectedText.length > 100
154
+ ? expectedText.slice(0, 100) + "…"
155
+ : expectedText })] })) : null, pageDiffers && (_jsxs("div", { children: [_jsx("span", { className: "text-gray-500 dark:text-gray-400 font-medium uppercase text-[10px]", children: "Page" }), _jsxs("p", { className: "mt-1 font-mono text-[11px] text-gray-700 dark:text-gray-300", children: [_jsx("span", { className: "text-red-600 dark:text-red-400 line-through opacity-70", children: expectedPage }), " → ", actualPage] })] })), lineIdDiffers && (_jsxs("div", { children: [_jsx("span", { className: "text-gray-500 dark:text-gray-400 font-medium uppercase text-[10px]", children: "Line" }), _jsxs("p", { className: "mt-1 font-mono text-[11px] text-gray-700 dark:text-gray-300", children: [_jsx("span", { className: "text-red-600 dark:text-red-400 line-through opacity-70", children: expectedLineIds?.join(", ") }), " → ", actualLineIds?.join(", ")] })] }))] }));
149
156
  }
150
157
  // =============================================================================
151
158
  // MAIN COMPONENT
@@ -193,7 +200,8 @@ export const CitationComponent = forwardRef(({ citation, children, className, hi
193
200
  if (typeof actions.setImageExpanded === "string") {
194
201
  setExpandedImageSrc(actions.setImageExpanded);
195
202
  }
196
- else if (actions.setImageExpanded === true && verification?.verificationImageBase64) {
203
+ else if (actions.setImageExpanded === true &&
204
+ verification?.verificationImageBase64) {
197
205
  setExpandedImageSrc(verification.verificationImageBase64);
198
206
  }
199
207
  else if (actions.setImageExpanded === false) {
@@ -240,14 +248,26 @@ export const CitationComponent = forwardRef(({ citation, children, className, hi
240
248
  behaviorConfig.onHover.onEnter(getBehaviorContext());
241
249
  }
242
250
  eventHandlers?.onMouseEnter?.(citation, citationKey);
243
- }, [eventHandlers, behaviorConfig, citation, citationKey, getBehaviorContext]);
251
+ }, [
252
+ eventHandlers,
253
+ behaviorConfig,
254
+ citation,
255
+ citationKey,
256
+ getBehaviorContext,
257
+ ]);
244
258
  const handleMouseLeave = useCallback(() => {
245
259
  setIsHovering(false);
246
260
  if (behaviorConfig?.onHover?.onLeave) {
247
261
  behaviorConfig.onHover.onLeave(getBehaviorContext());
248
262
  }
249
263
  eventHandlers?.onMouseLeave?.(citation, citationKey);
250
- }, [eventHandlers, behaviorConfig, citation, citationKey, getBehaviorContext]);
264
+ }, [
265
+ eventHandlers,
266
+ behaviorConfig,
267
+ citation,
268
+ citationKey,
269
+ getBehaviorContext,
270
+ ]);
251
271
  // Touch handler for mobile
252
272
  const handleTouchEnd = useCallback((e) => {
253
273
  if (isMobile) {
@@ -257,13 +277,18 @@ export const CitationComponent = forwardRef(({ citation, children, className, hi
257
277
  }
258
278
  }, [eventHandlers, citation, citationKey, isMobile]);
259
279
  // Early return for miss with fallback display
260
- if (fallbackDisplay !== null && fallbackDisplay !== undefined && !hideKeySpan && isMiss) {
280
+ if (fallbackDisplay !== null &&
281
+ fallbackDisplay !== undefined &&
282
+ !hideKeySpan &&
283
+ isMiss) {
261
284
  return (_jsx("span", { className: cn("text-gray-400 dark:text-gray-500", className), children: fallbackDisplay }));
262
285
  }
263
286
  // Status classes for text styling
264
287
  const statusClasses = cn(
265
288
  // Found status (text color) - verified or partial match
266
- (isVerified || isPartialMatch) && variant === "brackets" && "text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 hover:underline", isMiss && "opacity-70 line-through text-gray-400 dark:text-gray-500", isPending && "text-gray-500 dark:text-gray-400");
289
+ (isVerified || isPartialMatch) &&
290
+ variant === "brackets" &&
291
+ "text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 hover:underline", isMiss && "opacity-70 line-through text-gray-400 dark:text-gray-500", isPending && "text-gray-500 dark:text-gray-400");
267
292
  // Render indicator based on status priority:
268
293
  // 1. Custom renderIndicator (if provided)
269
294
  // 2. Pending → Spinner
@@ -310,7 +335,8 @@ export const CitationComponent = forwardRef(({ citation, children, className, hi
310
335
  const isPopoverHidden = popoverPosition === "hidden";
311
336
  const shouldShowPopover = !isPopoverHidden &&
312
337
  verification &&
313
- (verification.verificationImageBase64 || verification.verifiedMatchSnippet);
338
+ (verification.verificationImageBase64 ||
339
+ verification.verifiedMatchSnippet);
314
340
  const hasImage = !!verification?.verificationImageBase64;
315
341
  // Image overlay
316
342
  const imageOverlay = expandedImageSrc ? (_jsx(ImageOverlay, { src: expandedImageSrc, alt: "Citation verification - full size", onClose: () => setExpandedImageSrc(null) })) : null;
@@ -6,3 +6,7 @@ export declare const CheckIcon: () => import("react/jsx-runtime").JSX.Element;
6
6
  * Warning icon SVG (no dependencies)
7
7
  */
8
8
  export declare const WarningIcon: () => import("react/jsx-runtime").JSX.Element;
9
+ /** Spinner component for loading/pending state */
10
+ export declare const SpinnerIcon: ({ className }: {
11
+ className?: string;
12
+ }) => import("react/jsx-runtime").JSX.Element;
@@ -1,4 +1,5 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "./utils.js";
2
3
  /**
3
4
  * Check icon SVG (no dependencies)
4
5
  */
@@ -7,3 +8,5 @@ export const CheckIcon = () => (_jsx("svg", { className: "dc-check-icon", viewBo
7
8
  * Warning icon SVG (no dependencies)
8
9
  */
9
10
  export const WarningIcon = () => (_jsx("svg", { className: "dc-status-icon", viewBox: "0 0 256 256", fill: "currentColor", "aria-hidden": "true", children: _jsx("path", { d: "M236.8,188.09,149.35,36.22h0a24.76,24.76,0,0,0-42.7,0L19.2,188.09a23.51,23.51,0,0,0,0,23.72A24.35,24.35,0,0,0,40.55,224h174.9a24.35,24.35,0,0,0,21.33-12.19A23.51,23.51,0,0,0,236.8,188.09ZM120,104a8,8,0,0,1,16,0v40a8,8,0,0,1-16,0Zm8,88a12,12,0,1,1,12-12A12,12,0,0,1,128,192Z" }) }));
11
+ /** Spinner component for loading/pending state */
12
+ export const SpinnerIcon = ({ className }) => (_jsxs("svg", { className: cn("animate-spin", className), xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", width: "12", height: "12", children: [_jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), _jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] }));
@@ -1,5 +1,6 @@
1
1
  import type { Citation } from "../types/citation.js";
2
2
  import type { Verification } from "../types/verification.js";
3
+ export declare function cn(...classes: (string | undefined | null | false)[]): string;
3
4
  /**
4
5
  * Generates a unique, deterministic key for a citation based on its content.
5
6
  * @param citation - The citation to generate a key for
@@ -1,5 +1,11 @@
1
1
  import { sha1Hash } from "../utils/sha.js";
2
2
  import { getCitationPageNumber } from "../parsing/normalizeCitation.js";
3
+ // =============================================================================
4
+ // UTILITY FUNCTIONS
5
+ // =============================================================================
6
+ export function cn(...classes) {
7
+ return classes.filter(Boolean).join(" ");
8
+ }
3
9
  /**
4
10
  * Generates a unique, deterministic key for a citation based on its content.
5
11
  * @param citation - The citation to generate a key for
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepcitation/deepcitation-js",
3
- "version": "1.1.24",
3
+ "version": "1.1.25",
4
4
  "description": "DeepCitation JavaScript SDK for deterministic AI citation verification",
5
5
  "type": "module",
6
6
  "private": false,