@deepcitation/deepcitation-js 1.1.16 → 1.1.18
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/lib/client/DeepCitation.d.ts +13 -13
- package/lib/client/DeepCitation.js +32 -32
- package/lib/client/types.d.ts +14 -14
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/parsing/normalizeCitation.js +4 -4
- package/lib/parsing/parseCitation.d.ts +14 -14
- package/lib/parsing/parseCitation.js +30 -30
- package/lib/prompts/citationPrompts.d.ts +4 -4
- package/lib/prompts/citationPrompts.js +5 -5
- package/lib/prompts/promptCompression.js +5 -1
- package/lib/react/styles.css +4 -2
- package/lib/react/utils.js +1 -1
- package/lib/types/citation.d.ts +2 -2
- package/lib/types/verification.d.ts +1 -1
- package/lib/types/verification.js +3 -3
- package/package.json +1 -1
|
@@ -10,7 +10,7 @@ import type { CitationInput, ConvertFileInput, ConvertFileResponse, DeepCitation
|
|
|
10
10
|
* const dc = new DeepCitation({ apiKey: process.env.DEEPCITATION_API_KEY });
|
|
11
11
|
*
|
|
12
12
|
* // Upload a file
|
|
13
|
-
* const {
|
|
13
|
+
* const { attachmentId, promptContent } = await dc.uploadFile(file);
|
|
14
14
|
*
|
|
15
15
|
* // Include promptContent in your LLM messages
|
|
16
16
|
* const response = await llm.chat({
|
|
@@ -22,7 +22,7 @@ import type { CitationInput, ConvertFileInput, ConvertFileResponse, DeepCitation
|
|
|
22
22
|
*
|
|
23
23
|
* // Verify citations in the LLM output
|
|
24
24
|
* const citations = getAllCitationsFromLlmOutput(response);
|
|
25
|
-
* const verified = await dc.verifyCitations(
|
|
25
|
+
* const verified = await dc.verifyCitations(attachmentId, citations);
|
|
26
26
|
* ```
|
|
27
27
|
*/
|
|
28
28
|
export declare class DeepCitation {
|
|
@@ -45,7 +45,7 @@ export declare class DeepCitation {
|
|
|
45
45
|
*
|
|
46
46
|
* @param file - The file to upload (File, Blob, or Buffer)
|
|
47
47
|
* @param options - Optional upload options
|
|
48
|
-
* @returns Upload response with
|
|
48
|
+
* @returns Upload response with attachmentId and extracted text
|
|
49
49
|
*
|
|
50
50
|
* @example
|
|
51
51
|
* ```typescript
|
|
@@ -86,8 +86,8 @@ export declare class DeepCitation {
|
|
|
86
86
|
* });
|
|
87
87
|
*
|
|
88
88
|
* // Then prepare the file for verification
|
|
89
|
-
* const { deepTextPromptPortion,
|
|
90
|
-
*
|
|
89
|
+
* const { deepTextPromptPortion, attachmentId } = await dc.prepareConvertedFile({
|
|
90
|
+
* attachmentId: result.attachmentId
|
|
91
91
|
* });
|
|
92
92
|
* ```
|
|
93
93
|
*/
|
|
@@ -96,8 +96,8 @@ export declare class DeepCitation {
|
|
|
96
96
|
* Prepare a previously converted file for citation verification.
|
|
97
97
|
* Use this after calling convertToPdf() to extract text and get deepTextPromptPortion.
|
|
98
98
|
*
|
|
99
|
-
* @param options - Options with
|
|
100
|
-
* @returns Upload response with
|
|
99
|
+
* @param options - Options with attachmentId from convertFile
|
|
100
|
+
* @returns Upload response with attachmentId and extracted text
|
|
101
101
|
*
|
|
102
102
|
* @example
|
|
103
103
|
* ```typescript
|
|
@@ -105,8 +105,8 @@ export declare class DeepCitation {
|
|
|
105
105
|
* const converted = await dc.convertToPdf({ url: "https://example.com/article" });
|
|
106
106
|
*
|
|
107
107
|
* // Then prepare it for verification
|
|
108
|
-
* const { deepTextPromptPortion,
|
|
109
|
-
*
|
|
108
|
+
* const { deepTextPromptPortion, attachmentId } = await dc.prepareConvertedFile({
|
|
109
|
+
* attachmentId: converted.attachmentId
|
|
110
110
|
* });
|
|
111
111
|
*
|
|
112
112
|
* // Use deepTextPromptPortion in your LLM prompt...
|
|
@@ -117,7 +117,7 @@ export declare class DeepCitation {
|
|
|
117
117
|
* Upload multiple files for citation verification and get structured content.
|
|
118
118
|
* This is the recommended way to prepare files for LLM prompts.
|
|
119
119
|
*
|
|
120
|
-
* @param files - Array of files to upload with optional filenames and
|
|
120
|
+
* @param files - Array of files to upload with optional filenames and attachmentIds
|
|
121
121
|
* @returns Object containing fileDataParts for verification and deepTextPromptPortion for LLM
|
|
122
122
|
*
|
|
123
123
|
* @example
|
|
@@ -142,7 +142,7 @@ export declare class DeepCitation {
|
|
|
142
142
|
/**
|
|
143
143
|
* Verify citations against a previously uploaded file.
|
|
144
144
|
*
|
|
145
|
-
* @param
|
|
145
|
+
* @param attachmentId - The attachment ID returned from uploadFile
|
|
146
146
|
* @param citations - Citations to verify (from getAllCitationsFromLlmOutput)
|
|
147
147
|
* @param options - Optional verification options
|
|
148
148
|
* @returns Verification results with status and proof images
|
|
@@ -152,7 +152,7 @@ export declare class DeepCitation {
|
|
|
152
152
|
* import { getAllCitationsFromLlmOutput } from '@deepcitation/deepcitation-js';
|
|
153
153
|
*
|
|
154
154
|
* const citations = getAllCitationsFromLlmOutput(llmResponse);
|
|
155
|
-
* const verified = await dc.verifyCitations(
|
|
155
|
+
* const verified = await dc.verifyCitations(attachmentId, citations);
|
|
156
156
|
*
|
|
157
157
|
* for (const [key, result] of Object.entries(verified.verifications)) {
|
|
158
158
|
* console.log(key, result.searchState?.status);
|
|
@@ -160,7 +160,7 @@ export declare class DeepCitation {
|
|
|
160
160
|
* }
|
|
161
161
|
* ```
|
|
162
162
|
*/
|
|
163
|
-
verifyCitations(
|
|
163
|
+
verifyCitations(attachmentId: string, citations: CitationInput, options?: VerifyCitationsOptions): Promise<VerifyCitationsResponse>;
|
|
164
164
|
/**
|
|
165
165
|
* Verify citations from LLM output with automatic parsing.
|
|
166
166
|
* This is the recommended way to verify citations for new integrations.
|
|
@@ -31,7 +31,7 @@ async function extractErrorMessage(response, fallbackAction) {
|
|
|
31
31
|
* const dc = new DeepCitation({ apiKey: process.env.DEEPCITATION_API_KEY });
|
|
32
32
|
*
|
|
33
33
|
* // Upload a file
|
|
34
|
-
* const {
|
|
34
|
+
* const { attachmentId, promptContent } = await dc.uploadFile(file);
|
|
35
35
|
*
|
|
36
36
|
* // Include promptContent in your LLM messages
|
|
37
37
|
* const response = await llm.chat({
|
|
@@ -43,7 +43,7 @@ async function extractErrorMessage(response, fallbackAction) {
|
|
|
43
43
|
*
|
|
44
44
|
* // Verify citations in the LLM output
|
|
45
45
|
* const citations = getAllCitationsFromLlmOutput(response);
|
|
46
|
-
* const verified = await dc.verifyCitations(
|
|
46
|
+
* const verified = await dc.verifyCitations(attachmentId, citations);
|
|
47
47
|
* ```
|
|
48
48
|
*/
|
|
49
49
|
export class DeepCitation {
|
|
@@ -72,7 +72,7 @@ export class DeepCitation {
|
|
|
72
72
|
*
|
|
73
73
|
* @param file - The file to upload (File, Blob, or Buffer)
|
|
74
74
|
* @param options - Optional upload options
|
|
75
|
-
* @returns Upload response with
|
|
75
|
+
* @returns Upload response with attachmentId and extracted text
|
|
76
76
|
*
|
|
77
77
|
* @example
|
|
78
78
|
* ```typescript
|
|
@@ -89,8 +89,8 @@ export class DeepCitation {
|
|
|
89
89
|
const { blob, name } = toBlob(file, options?.filename);
|
|
90
90
|
const formData = new FormData();
|
|
91
91
|
formData.append("file", blob, name);
|
|
92
|
-
if (options?.
|
|
93
|
-
formData.append("
|
|
92
|
+
if (options?.attachmentId)
|
|
93
|
+
formData.append("attachmentId", options.attachmentId);
|
|
94
94
|
if (options?.filename)
|
|
95
95
|
formData.append("filename", options.filename);
|
|
96
96
|
const response = await fetch(`${this.apiUrl}/prepareFile`, {
|
|
@@ -130,14 +130,14 @@ export class DeepCitation {
|
|
|
130
130
|
* });
|
|
131
131
|
*
|
|
132
132
|
* // Then prepare the file for verification
|
|
133
|
-
* const { deepTextPromptPortion,
|
|
134
|
-
*
|
|
133
|
+
* const { deepTextPromptPortion, attachmentId } = await dc.prepareConvertedFile({
|
|
134
|
+
* attachmentId: result.attachmentId
|
|
135
135
|
* });
|
|
136
136
|
* ```
|
|
137
137
|
*/
|
|
138
138
|
async convertToPdf(input) {
|
|
139
139
|
const inputObj = typeof input === "string" ? { url: input } : input;
|
|
140
|
-
const { url, file, filename,
|
|
140
|
+
const { url, file, filename, attachmentId } = inputObj;
|
|
141
141
|
if (!url && !file) {
|
|
142
142
|
throw new Error("Either url or file must be provided");
|
|
143
143
|
}
|
|
@@ -149,15 +149,15 @@ export class DeepCitation {
|
|
|
149
149
|
Authorization: `Bearer ${this.apiKey}`,
|
|
150
150
|
"Content-Type": "application/json",
|
|
151
151
|
},
|
|
152
|
-
body: JSON.stringify({ url, filename,
|
|
152
|
+
body: JSON.stringify({ url, filename, attachmentId }),
|
|
153
153
|
});
|
|
154
154
|
}
|
|
155
155
|
else {
|
|
156
156
|
const { blob, name } = toBlob(file, filename);
|
|
157
157
|
const formData = new FormData();
|
|
158
158
|
formData.append("file", blob, name);
|
|
159
|
-
if (
|
|
160
|
-
formData.append("
|
|
159
|
+
if (attachmentId)
|
|
160
|
+
formData.append("attachmentId", attachmentId);
|
|
161
161
|
if (filename)
|
|
162
162
|
formData.append("filename", filename);
|
|
163
163
|
response = await fetch(`${this.apiUrl}/convertFile`, {
|
|
@@ -175,8 +175,8 @@ export class DeepCitation {
|
|
|
175
175
|
* Prepare a previously converted file for citation verification.
|
|
176
176
|
* Use this after calling convertToPdf() to extract text and get deepTextPromptPortion.
|
|
177
177
|
*
|
|
178
|
-
* @param options - Options with
|
|
179
|
-
* @returns Upload response with
|
|
178
|
+
* @param options - Options with attachmentId from convertFile
|
|
179
|
+
* @returns Upload response with attachmentId and extracted text
|
|
180
180
|
*
|
|
181
181
|
* @example
|
|
182
182
|
* ```typescript
|
|
@@ -184,8 +184,8 @@ export class DeepCitation {
|
|
|
184
184
|
* const converted = await dc.convertToPdf({ url: "https://example.com/article" });
|
|
185
185
|
*
|
|
186
186
|
* // Then prepare it for verification
|
|
187
|
-
* const { deepTextPromptPortion,
|
|
188
|
-
*
|
|
187
|
+
* const { deepTextPromptPortion, attachmentId } = await dc.prepareConvertedFile({
|
|
188
|
+
* attachmentId: converted.attachmentId
|
|
189
189
|
* });
|
|
190
190
|
*
|
|
191
191
|
* // Use deepTextPromptPortion in your LLM prompt...
|
|
@@ -199,7 +199,7 @@ export class DeepCitation {
|
|
|
199
199
|
"Content-Type": "application/json",
|
|
200
200
|
},
|
|
201
201
|
body: JSON.stringify({
|
|
202
|
-
|
|
202
|
+
attachmentId: options.attachmentId,
|
|
203
203
|
}),
|
|
204
204
|
});
|
|
205
205
|
if (!response.ok) {
|
|
@@ -211,7 +211,7 @@ export class DeepCitation {
|
|
|
211
211
|
* Upload multiple files for citation verification and get structured content.
|
|
212
212
|
* This is the recommended way to prepare files for LLM prompts.
|
|
213
213
|
*
|
|
214
|
-
* @param files - Array of files to upload with optional filenames and
|
|
214
|
+
* @param files - Array of files to upload with optional filenames and attachmentIds
|
|
215
215
|
* @returns Object containing fileDataParts for verification and deepTextPromptPortion for LLM
|
|
216
216
|
*
|
|
217
217
|
* @example
|
|
@@ -237,14 +237,14 @@ export class DeepCitation {
|
|
|
237
237
|
return { fileDataParts: [], deepTextPromptPortion: [] };
|
|
238
238
|
}
|
|
239
239
|
// Upload all files in parallel
|
|
240
|
-
const uploadPromises = files.map(({ file, filename,
|
|
240
|
+
const uploadPromises = files.map(({ file, filename, attachmentId }) => this.uploadFile(file, { filename, attachmentId }).then((result) => ({
|
|
241
241
|
result,
|
|
242
242
|
filename,
|
|
243
243
|
})));
|
|
244
244
|
const uploadResults = await Promise.all(uploadPromises);
|
|
245
245
|
// Extract file data parts with deepTextPromptPortion included (single source of truth)
|
|
246
246
|
const fileDataParts = uploadResults.map(({ result, filename }) => ({
|
|
247
|
-
|
|
247
|
+
attachmentId: result.attachmentId,
|
|
248
248
|
deepTextPromptPortion: result.deepTextPromptPortion,
|
|
249
249
|
filename: filename || result.metadata?.filename,
|
|
250
250
|
}));
|
|
@@ -255,7 +255,7 @@ export class DeepCitation {
|
|
|
255
255
|
/**
|
|
256
256
|
* Verify citations against a previously uploaded file.
|
|
257
257
|
*
|
|
258
|
-
* @param
|
|
258
|
+
* @param attachmentId - The attachment ID returned from uploadFile
|
|
259
259
|
* @param citations - Citations to verify (from getAllCitationsFromLlmOutput)
|
|
260
260
|
* @param options - Optional verification options
|
|
261
261
|
* @returns Verification results with status and proof images
|
|
@@ -265,7 +265,7 @@ export class DeepCitation {
|
|
|
265
265
|
* import { getAllCitationsFromLlmOutput } from '@deepcitation/deepcitation-js';
|
|
266
266
|
*
|
|
267
267
|
* const citations = getAllCitationsFromLlmOutput(llmResponse);
|
|
268
|
-
* const verified = await dc.verifyCitations(
|
|
268
|
+
* const verified = await dc.verifyCitations(attachmentId, citations);
|
|
269
269
|
*
|
|
270
270
|
* for (const [key, result] of Object.entries(verified.verifications)) {
|
|
271
271
|
* console.log(key, result.searchState?.status);
|
|
@@ -273,7 +273,7 @@ export class DeepCitation {
|
|
|
273
273
|
* }
|
|
274
274
|
* ```
|
|
275
275
|
*/
|
|
276
|
-
async verifyCitations(
|
|
276
|
+
async verifyCitations(attachmentId, citations, options) {
|
|
277
277
|
// Normalize citations to a map with citation keys
|
|
278
278
|
const citationMap = {};
|
|
279
279
|
if (Array.isArray(citations)) {
|
|
@@ -301,7 +301,7 @@ export class DeepCitation {
|
|
|
301
301
|
const requestUrl = `${this.apiUrl}/verifyCitations`;
|
|
302
302
|
const requestBody = {
|
|
303
303
|
data: {
|
|
304
|
-
|
|
304
|
+
attachmentId,
|
|
305
305
|
citations: citationMap,
|
|
306
306
|
outputImageFormat: options?.outputImageFormat || "avif",
|
|
307
307
|
},
|
|
@@ -348,20 +348,20 @@ export class DeepCitation {
|
|
|
348
348
|
if (Object.keys(citations).length === 0) {
|
|
349
349
|
return { verifications: {} };
|
|
350
350
|
}
|
|
351
|
-
// Group citations by
|
|
352
|
-
const
|
|
351
|
+
// Group citations by attachmentId
|
|
352
|
+
const citationsByAttachment = new Map();
|
|
353
353
|
for (const [key, citation] of Object.entries(citations)) {
|
|
354
|
-
const
|
|
355
|
-
if (!
|
|
356
|
-
|
|
354
|
+
const attachmentId = citation.attachmentId || "";
|
|
355
|
+
if (!citationsByAttachment.has(attachmentId)) {
|
|
356
|
+
citationsByAttachment.set(attachmentId, {});
|
|
357
357
|
}
|
|
358
|
-
|
|
358
|
+
citationsByAttachment.get(attachmentId)[key] = citation;
|
|
359
359
|
}
|
|
360
360
|
// Verify all files in parallel
|
|
361
361
|
const verificationPromises = [];
|
|
362
|
-
for (const [
|
|
363
|
-
if (
|
|
364
|
-
verificationPromises.push(this.verifyCitations(
|
|
362
|
+
for (const [attachmentId, fileCitations] of citationsByAttachment) {
|
|
363
|
+
if (attachmentId) {
|
|
364
|
+
verificationPromises.push(this.verifyCitations(attachmentId, fileCitations, { outputImageFormat }));
|
|
365
365
|
}
|
|
366
366
|
}
|
|
367
367
|
const results = await Promise.all(verificationPromises);
|
package/lib/client/types.d.ts
CHANGED
|
@@ -12,8 +12,8 @@ export interface DeepCitationConfig {
|
|
|
12
12
|
* Response from uploading a file for citation verification
|
|
13
13
|
*/
|
|
14
14
|
export interface UploadFileResponse {
|
|
15
|
-
/** The
|
|
16
|
-
|
|
15
|
+
/** The attachment ID assigned by DeepCitation (custom or auto-generated) */
|
|
16
|
+
attachmentId: string;
|
|
17
17
|
/** The full text content formatted for LLM prompts with page markers and line IDs. Use this in your user prompts. */
|
|
18
18
|
deepTextPromptPortion: string;
|
|
19
19
|
/** Form fields extracted from PDF forms */
|
|
@@ -41,8 +41,8 @@ export interface UploadFileResponse {
|
|
|
41
41
|
* Options for file upload
|
|
42
42
|
*/
|
|
43
43
|
export interface UploadFileOptions {
|
|
44
|
-
/** Optional custom
|
|
45
|
-
|
|
44
|
+
/** Optional custom attachment ID to use instead of auto-generated one */
|
|
45
|
+
attachmentId?: string;
|
|
46
46
|
/** Optional custom filename (uses File.name if not provided) */
|
|
47
47
|
filename?: string;
|
|
48
48
|
}
|
|
@@ -72,15 +72,15 @@ export interface FileInput {
|
|
|
72
72
|
file: File | Blob | Buffer;
|
|
73
73
|
/** Optional filename */
|
|
74
74
|
filename?: string;
|
|
75
|
-
/** Optional custom
|
|
76
|
-
|
|
75
|
+
/** Optional custom attachment ID */
|
|
76
|
+
attachmentId?: string;
|
|
77
77
|
}
|
|
78
78
|
/**
|
|
79
79
|
* File reference returned from prepareFiles
|
|
80
80
|
*/
|
|
81
81
|
export interface FileDataPart {
|
|
82
|
-
/** The
|
|
83
|
-
|
|
82
|
+
/** The attachment ID assigned by DeepCitation */
|
|
83
|
+
attachmentId: string;
|
|
84
84
|
/** The formatted text content for LLM prompts (with page markers and line IDs) */
|
|
85
85
|
deepTextPromptPortion: string;
|
|
86
86
|
/** Optional filename for display purposes */
|
|
@@ -120,15 +120,15 @@ export interface ConvertFileInput {
|
|
|
120
120
|
file?: File | Blob | Buffer;
|
|
121
121
|
/** Optional custom filename for the converted PDF */
|
|
122
122
|
filename?: string;
|
|
123
|
-
/** Optional custom
|
|
124
|
-
|
|
123
|
+
/** Optional custom attachment ID */
|
|
124
|
+
attachmentId?: string;
|
|
125
125
|
}
|
|
126
126
|
/**
|
|
127
127
|
* Response from convertFile
|
|
128
128
|
*/
|
|
129
129
|
export interface ConvertFileResponse {
|
|
130
|
-
/** The
|
|
131
|
-
|
|
130
|
+
/** The attachment ID assigned by DeepCitation. Pass this to prepareConvertedFile(). */
|
|
131
|
+
attachmentId: string;
|
|
132
132
|
/** Metadata about the conversion */
|
|
133
133
|
metadata: {
|
|
134
134
|
/** Original filename before conversion */
|
|
@@ -149,6 +149,6 @@ export interface ConvertFileResponse {
|
|
|
149
149
|
* Options for processing a converted file
|
|
150
150
|
*/
|
|
151
151
|
export interface PrepareConvertedFileOptions {
|
|
152
|
-
/** The
|
|
153
|
-
|
|
152
|
+
/** The attachment ID from a previous convertFile call */
|
|
153
|
+
attachmentId: string;
|
|
154
154
|
}
|
package/lib/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export { DeepCitation } from "./client/index.js";
|
|
6
6
|
export type { DeepCitationConfig, UploadFileResponse, UploadFileOptions, VerifyCitationsResponse, VerifyCitationsOptions, CitationInput, FileInput, FileDataPart, PrepareFilesResult, VerifyCitationsFromLlmOutput, } from "./client/index.js";
|
|
7
|
-
export { parseCitation, getCitationStatus, getAllCitationsFromLlmOutput,
|
|
7
|
+
export { parseCitation, getCitationStatus, getAllCitationsFromLlmOutput, groupCitationsByAttachmentId, groupCitationsByAttachmentIdObject, } from "./parsing/parseCitation.js";
|
|
8
8
|
export { normalizeCitations, getCitationPageNumber, } from "./parsing/normalizeCitation.js";
|
|
9
9
|
export { isGeminiGarbage, cleanRepeatingLastSentence, } from "./parsing/parseWorkAround.js";
|
|
10
10
|
export type { Citation, CitationStatus, VerifyCitationRequest, VerifyCitationResponse, OutputImageFormat, } from "./types/citation.js";
|
package/lib/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// Client
|
|
6
6
|
export { DeepCitation } from "./client/index.js";
|
|
7
7
|
// Parsing
|
|
8
|
-
export { parseCitation, getCitationStatus, getAllCitationsFromLlmOutput,
|
|
8
|
+
export { parseCitation, getCitationStatus, getAllCitationsFromLlmOutput, groupCitationsByAttachmentId, groupCitationsByAttachmentIdObject, } from "./parsing/parseCitation.js";
|
|
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";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const removeCitations = (pageText, leaveValueBehind) => {
|
|
2
|
-
const citationRegex = /<cite\s+fileId='(\w{0,25})'\s+start_page[\_a-zA-Z]*='page[\_a-zA-Z]*(\d+)_index_(\d+)'\s+full_phrase='((?:[^'\\]|\\.)*)'\s+line(?:_ids|Ids)='([^']+)'(?:\s+(value|reasoning)='((?:[^'\\]|\\.)*)')?\s*\/>/g;
|
|
3
|
-
return pageText.replace(citationRegex, (match,
|
|
2
|
+
const citationRegex = /<cite\s+(?:fileId|attachmentId)='(\w{0,25})'\s+start_page[\_a-zA-Z]*='page[\_a-zA-Z]*(\d+)_index_(\d+)'\s+full_phrase='((?:[^'\\]|\\.)*)'\s+line(?:_ids|Ids)='([^']+)'(?:\s+(value|reasoning)='((?:[^'\\]|\\.)*)')?\s*\/>/g;
|
|
3
|
+
return pageText.replace(citationRegex, (match, attachmentId, pageNumber, index, fullPhrase, lineIds, value) => {
|
|
4
4
|
//it is still value= so we need to remove the value=
|
|
5
5
|
if (leaveValueBehind) {
|
|
6
6
|
return value?.replace(/value=['"]|['"]/g, "") || "";
|
|
@@ -53,7 +53,7 @@ const normalizeCitationContent = (input) => {
|
|
|
53
53
|
key === "start_pageKey" ||
|
|
54
54
|
key === "start_page_key")
|
|
55
55
|
return "start_page_key";
|
|
56
|
-
if (key === "fileID" || key === "fileId" || key === "file_id")
|
|
56
|
+
if (key === "fileID" || key === "fileId" || key === "file_id" || key === "attachmentId" || key === "attachment_id")
|
|
57
57
|
return "file_id";
|
|
58
58
|
if (key === "keySpan" || key === "key_span")
|
|
59
59
|
return "key_span";
|
|
@@ -71,7 +71,7 @@ const normalizeCitationContent = (input) => {
|
|
|
71
71
|
// 2. ROBUST TEXT ATTRIBUTE PARSING (reasoning, value, full_phrase)
|
|
72
72
|
// This regex matches: Key = Quote -> Content (lazy) -> Lookahead for (Next Attribute OR End of Tag)
|
|
73
73
|
// It effectively ignores quotes inside the content during the initial capture.
|
|
74
|
-
const textAttributeRegex = /(fullPhrase|full_phrase|keySpan|key_span|reasoning|value)\s*=\s*(['"])([\s\S]*?)(?=\s+(?:line_ids|lineIds|timestamps|fileId|file_id|start_page_key|start_pageKey|startPageKey|keySpan|key_span|reasoning|value|full_phrase)|\s*\/?>)/gm;
|
|
74
|
+
const textAttributeRegex = /(fullPhrase|full_phrase|keySpan|key_span|reasoning|value)\s*=\s*(['"])([\s\S]*?)(?=\s+(?:line_ids|lineIds|timestamps|fileId|file_id|attachmentId|attachment_id|start_page_key|start_pageKey|startPageKey|keySpan|key_span|reasoning|value|full_phrase)|\s*\/?>)/gm;
|
|
75
75
|
normalized = normalized.replace(textAttributeRegex, (_match, key, openQuote, rawContent) => {
|
|
76
76
|
let content = rawContent;
|
|
77
77
|
// The lazy match usually captures the closing quote because the lookahead
|
|
@@ -27,53 +27,53 @@ export declare const getAllCitationsFromLlmOutput: (llmOutput: any) => {
|
|
|
27
27
|
[key: string]: Citation;
|
|
28
28
|
};
|
|
29
29
|
/**
|
|
30
|
-
* Groups citations by their
|
|
30
|
+
* Groups citations by their attachmentId for multi-file verification scenarios.
|
|
31
31
|
* This is useful when you have citations from multiple files and need to
|
|
32
32
|
* verify them against their respective source documents.
|
|
33
33
|
*
|
|
34
34
|
* @param citations - Array of Citation objects or a dictionary of citations
|
|
35
|
-
* @returns Map of
|
|
35
|
+
* @returns Map of attachmentId to dictionary of citations from that file
|
|
36
36
|
*
|
|
37
37
|
* @example
|
|
38
38
|
* ```typescript
|
|
39
39
|
* const citations = getAllCitationsFromLlmOutput(response.content);
|
|
40
|
-
* const
|
|
40
|
+
* const citationsByAttachment = groupCitationsByAttachmentId(citations);
|
|
41
41
|
*
|
|
42
42
|
* // Verify citations for each file
|
|
43
|
-
* for (const [
|
|
44
|
-
* const verified = await dc.verifyCitations(
|
|
43
|
+
* for (const [attachmentId, fileCitations] of citationsByAttachment) {
|
|
44
|
+
* const verified = await dc.verifyCitations(attachmentId, fileCitations);
|
|
45
45
|
* // Process verification results...
|
|
46
46
|
* }
|
|
47
47
|
* ```
|
|
48
48
|
*/
|
|
49
|
-
export declare function
|
|
49
|
+
export declare function groupCitationsByAttachmentId(citations: Citation[] | {
|
|
50
50
|
[key: string]: Citation;
|
|
51
51
|
}): Map<string, {
|
|
52
52
|
[key: string]: Citation;
|
|
53
53
|
}>;
|
|
54
54
|
/**
|
|
55
|
-
* Groups citations by their
|
|
56
|
-
* Alternative to
|
|
55
|
+
* Groups citations by their attachmentId and returns as a plain object.
|
|
56
|
+
* Alternative to groupCitationsByAttachmentId that returns a plain object instead of a Map.
|
|
57
57
|
*
|
|
58
58
|
* @param citations - Array of Citation objects or a dictionary of citations
|
|
59
|
-
* @returns Object with
|
|
59
|
+
* @returns Object with attachmentId keys mapping to citation dictionaries
|
|
60
60
|
*
|
|
61
61
|
* @example
|
|
62
62
|
* ```typescript
|
|
63
63
|
* const citations = getAllCitationsFromLlmOutput(response.content);
|
|
64
|
-
* const
|
|
64
|
+
* const citationsByAttachment = groupCitationsByAttachmentIdObject(citations);
|
|
65
65
|
*
|
|
66
66
|
* // Verify citations for each file using Promise.all
|
|
67
|
-
* const verificationPromises = Object.entries(
|
|
68
|
-
* ([
|
|
67
|
+
* const verificationPromises = Object.entries(citationsByAttachment).map(
|
|
68
|
+
* ([attachmentId, fileCitations]) => dc.verifyCitations(attachmentId, fileCitations)
|
|
69
69
|
* );
|
|
70
70
|
* const results = await Promise.all(verificationPromises);
|
|
71
71
|
* ```
|
|
72
72
|
*/
|
|
73
|
-
export declare function
|
|
73
|
+
export declare function groupCitationsByAttachmentIdObject(citations: Citation[] | {
|
|
74
74
|
[key: string]: Citation;
|
|
75
75
|
}): {
|
|
76
|
-
[
|
|
76
|
+
[attachmentId: string]: {
|
|
77
77
|
[key: string]: Citation;
|
|
78
78
|
};
|
|
79
79
|
};
|
|
@@ -88,7 +88,7 @@ export const parseCitation = (fragment, mdAttachmentId, citationCounterRef, isVe
|
|
|
88
88
|
: "";
|
|
89
89
|
const middleCite = fragment.substring(fragment.indexOf("<cite"), fragment.indexOf("/>") + 2);
|
|
90
90
|
// GROUPS:
|
|
91
|
-
// 1:
|
|
91
|
+
// 1: attachmentId
|
|
92
92
|
// 2: start_page number
|
|
93
93
|
// 3: index number
|
|
94
94
|
// 4: full_phrase content (escaped)
|
|
@@ -101,8 +101,8 @@ export const parseCitation = (fragment, mdAttachmentId, citationCounterRef, isVe
|
|
|
101
101
|
const match = citationMatches?.[0];
|
|
102
102
|
const pageNumber = match?.[2] ? parseInt(match?.[2]) : undefined;
|
|
103
103
|
const pageIndex = match?.[3] ? parseInt(match?.[3]) : undefined;
|
|
104
|
-
let
|
|
105
|
-
let attachmentId =
|
|
104
|
+
let rawAttachmentId = match?.[1];
|
|
105
|
+
let attachmentId = rawAttachmentId?.length === 20 ? rawAttachmentId : mdAttachmentId || rawAttachmentId;
|
|
106
106
|
// Use helper to handle escaped quotes inside the phrase
|
|
107
107
|
let fullPhrase = cleanAndUnescape(match?.[4]);
|
|
108
108
|
let keySpan = cleanAndUnescape(match?.[5]);
|
|
@@ -128,7 +128,7 @@ export const parseCitation = (fragment, mdAttachmentId, citationCounterRef, isVe
|
|
|
128
128
|
console.error("Error parsing lineIds", e);
|
|
129
129
|
}
|
|
130
130
|
// GROUPS for AV:
|
|
131
|
-
// 1:
|
|
131
|
+
// 1: attachmentId
|
|
132
132
|
// 2: full_phrase content (escaped)
|
|
133
133
|
// 3: timestamps content
|
|
134
134
|
// 4: Optional Key (value|reasoning)
|
|
@@ -138,8 +138,8 @@ export const parseCitation = (fragment, mdAttachmentId, citationCounterRef, isVe
|
|
|
138
138
|
const avMatch = avCitationMatches?.[0];
|
|
139
139
|
let timestamps;
|
|
140
140
|
if (avMatch) {
|
|
141
|
-
|
|
142
|
-
attachmentId =
|
|
141
|
+
rawAttachmentId = avMatch?.[1];
|
|
142
|
+
attachmentId = rawAttachmentId?.length === 20 ? rawAttachmentId : mdAttachmentId || rawAttachmentId;
|
|
143
143
|
fullPhrase = cleanAndUnescape(avMatch?.[2]);
|
|
144
144
|
const timestampsString = avMatch?.[3]?.replace(/timestamps=['"]|['"]/g, "");
|
|
145
145
|
const [startTime, endTime] = timestampsString?.split("-") || [];
|
|
@@ -154,7 +154,7 @@ export const parseCitation = (fragment, mdAttachmentId, citationCounterRef, isVe
|
|
|
154
154
|
timestamps = { startTime, endTime };
|
|
155
155
|
}
|
|
156
156
|
const citation = {
|
|
157
|
-
|
|
157
|
+
attachmentId: attachmentId,
|
|
158
158
|
pageNumber,
|
|
159
159
|
startPageKey: `page_number_${pageNumber || 1}_index_${pageIndex || 0}`,
|
|
160
160
|
fullPhrase,
|
|
@@ -188,7 +188,7 @@ const parseJsonCitation = (jsonCitation, citationNumber) => {
|
|
|
188
188
|
const startPageKey = jsonCitation.startPageKey ?? jsonCitation.start_page_key;
|
|
189
189
|
const keySpan = jsonCitation.keySpan ?? jsonCitation.key_span;
|
|
190
190
|
const rawLineIds = jsonCitation.lineIds ?? jsonCitation.line_ids;
|
|
191
|
-
const
|
|
191
|
+
const attachmentId = jsonCitation.attachmentId ?? jsonCitation.attachment_id ?? jsonCitation.fileId ?? jsonCitation.file_id;
|
|
192
192
|
const reasoning = jsonCitation.reasoning;
|
|
193
193
|
const value = jsonCitation.value;
|
|
194
194
|
if (!fullPhrase) {
|
|
@@ -215,7 +215,7 @@ const parseJsonCitation = (jsonCitation, citationNumber) => {
|
|
|
215
215
|
? [...rawLineIds].sort((a, b) => a - b)
|
|
216
216
|
: undefined;
|
|
217
217
|
const citation = {
|
|
218
|
-
|
|
218
|
+
attachmentId,
|
|
219
219
|
pageNumber,
|
|
220
220
|
fullPhrase,
|
|
221
221
|
citationNumber,
|
|
@@ -363,71 +363,71 @@ export const getAllCitationsFromLlmOutput = (llmOutput) => {
|
|
|
363
363
|
return citations;
|
|
364
364
|
};
|
|
365
365
|
/**
|
|
366
|
-
* Groups citations by their
|
|
366
|
+
* Groups citations by their attachmentId for multi-file verification scenarios.
|
|
367
367
|
* This is useful when you have citations from multiple files and need to
|
|
368
368
|
* verify them against their respective source documents.
|
|
369
369
|
*
|
|
370
370
|
* @param citations - Array of Citation objects or a dictionary of citations
|
|
371
|
-
* @returns Map of
|
|
371
|
+
* @returns Map of attachmentId to dictionary of citations from that file
|
|
372
372
|
*
|
|
373
373
|
* @example
|
|
374
374
|
* ```typescript
|
|
375
375
|
* const citations = getAllCitationsFromLlmOutput(response.content);
|
|
376
|
-
* const
|
|
376
|
+
* const citationsByAttachment = groupCitationsByAttachmentId(citations);
|
|
377
377
|
*
|
|
378
378
|
* // Verify citations for each file
|
|
379
|
-
* for (const [
|
|
380
|
-
* const verified = await dc.verifyCitations(
|
|
379
|
+
* for (const [attachmentId, fileCitations] of citationsByAttachment) {
|
|
380
|
+
* const verified = await dc.verifyCitations(attachmentId, fileCitations);
|
|
381
381
|
* // Process verification results...
|
|
382
382
|
* }
|
|
383
383
|
* ```
|
|
384
384
|
*/
|
|
385
|
-
export function
|
|
385
|
+
export function groupCitationsByAttachmentId(citations) {
|
|
386
386
|
const grouped = new Map();
|
|
387
387
|
// Normalize input to entries
|
|
388
388
|
const entries = Array.isArray(citations)
|
|
389
389
|
? citations.map((c, idx) => [generateCitationKey(c) || String(idx + 1), c])
|
|
390
390
|
: Object.entries(citations);
|
|
391
391
|
for (const [key, citation] of entries) {
|
|
392
|
-
const
|
|
393
|
-
if (!grouped.has(
|
|
394
|
-
grouped.set(
|
|
392
|
+
const attachmentId = citation.attachmentId || "";
|
|
393
|
+
if (!grouped.has(attachmentId)) {
|
|
394
|
+
grouped.set(attachmentId, {});
|
|
395
395
|
}
|
|
396
|
-
grouped.get(
|
|
396
|
+
grouped.get(attachmentId)[key] = citation;
|
|
397
397
|
}
|
|
398
398
|
return grouped;
|
|
399
399
|
}
|
|
400
400
|
/**
|
|
401
|
-
* Groups citations by their
|
|
402
|
-
* Alternative to
|
|
401
|
+
* Groups citations by their attachmentId and returns as a plain object.
|
|
402
|
+
* Alternative to groupCitationsByAttachmentId that returns a plain object instead of a Map.
|
|
403
403
|
*
|
|
404
404
|
* @param citations - Array of Citation objects or a dictionary of citations
|
|
405
|
-
* @returns Object with
|
|
405
|
+
* @returns Object with attachmentId keys mapping to citation dictionaries
|
|
406
406
|
*
|
|
407
407
|
* @example
|
|
408
408
|
* ```typescript
|
|
409
409
|
* const citations = getAllCitationsFromLlmOutput(response.content);
|
|
410
|
-
* const
|
|
410
|
+
* const citationsByAttachment = groupCitationsByAttachmentIdObject(citations);
|
|
411
411
|
*
|
|
412
412
|
* // Verify citations for each file using Promise.all
|
|
413
|
-
* const verificationPromises = Object.entries(
|
|
414
|
-
* ([
|
|
413
|
+
* const verificationPromises = Object.entries(citationsByAttachment).map(
|
|
414
|
+
* ([attachmentId, fileCitations]) => dc.verifyCitations(attachmentId, fileCitations)
|
|
415
415
|
* );
|
|
416
416
|
* const results = await Promise.all(verificationPromises);
|
|
417
417
|
* ```
|
|
418
418
|
*/
|
|
419
|
-
export function
|
|
419
|
+
export function groupCitationsByAttachmentIdObject(citations) {
|
|
420
420
|
const grouped = {};
|
|
421
421
|
// Normalize input to entries
|
|
422
422
|
const entries = Array.isArray(citations)
|
|
423
423
|
? citations.map((c, idx) => [generateCitationKey(c) || String(idx + 1), c])
|
|
424
424
|
: Object.entries(citations);
|
|
425
425
|
for (const [key, citation] of entries) {
|
|
426
|
-
const
|
|
427
|
-
if (!grouped[
|
|
428
|
-
grouped[
|
|
426
|
+
const attachmentId = citation.attachmentId || "";
|
|
427
|
+
if (!grouped[attachmentId]) {
|
|
428
|
+
grouped[attachmentId] = {};
|
|
429
429
|
}
|
|
430
|
-
grouped[
|
|
430
|
+
grouped[attachmentId][key] = citation;
|
|
431
431
|
}
|
|
432
432
|
return grouped;
|
|
433
433
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export declare const CITATION_MARKDOWN_SYNTAX_PROMPT = "\nCitation syntax to use within Markdown:\n\u2022 To support any ideas or information that requires a citation from the provided content, use the following citation syntax:\n<cite file_id='
|
|
2
|
-
export declare const AV_CITATION_MARKDOWN_SYNTAX_PROMPT = "\n\u2022 To support any ideas or information that requires a citation from the provided content, use the following citation syntax:\n<cite file_id='
|
|
1
|
+
export declare const CITATION_MARKDOWN_SYNTAX_PROMPT = "\nCitation syntax to use within Markdown:\n\u2022 To support any ideas or information that requires a citation from the provided content, use the following citation syntax:\n<cite file_id='attachment_id' start_page_key='page_number_PAGE_index_INDEX' full_phrase='the verbatim text of the terse phrase inside <file_text />; remember to escape quotes and newlines inside the full_phrase to remain as valid JSON' key_span='the verbatim 1-3 words within full_phrase that best support the citation' line_ids='2-6' reasoning='the terse logic used to conclude the citation' />\n\n\u2022 Very important: for page numbers, only use the page number and page index info from the page_number_PAGE_index_INDEX format (e.g. <page_number_1_index_0>) and never from the contents inside the page.\n\u2022 start_page_key, full_phrase, and line_ids are required for each citation.\n\u2022 Infer line_ids, as we only provide the first, last, and every 5th line. When copying a previous <cite />, use the full info from the previous citation without changing the start_page_key, line_ids, or any other <cite /> attributes.\n\u2022 Use refer to line_ids inclusively, and use a range (or single) for each citation, split multiple sequential line_ids into multiple citations.\n\u2022 These citations will be replaced and displayed in-line as a numeric element (e.g. [1]), the markdown preceding <cite /> should read naturally with only one <cite /> per sentence with rare exceptions for two <cite /> in a sentence. <cite /> often present best at the end of the sentence, and are not grouped at the end of the document.\n\u2022 The full_phrase should be the exact verbatim text of the phrase or paragraph from the source document to support the insight or idea.\n\u2022 We do NOT put the full_phrase inside <cite ...></cite>; we only use full_phrase inside the full_phrase attribute.\n";
|
|
2
|
+
export declare const AV_CITATION_MARKDOWN_SYNTAX_PROMPT = "\n\u2022 To support any ideas or information that requires a citation from the provided content, use the following citation syntax:\n<cite file_id='attachment_id' full_phrase='the verbatim text of the phrase; remember to escape quotes and newlines inside the full_phrase to remain as valid JSON' timestamps='HH:MM:SS.SSS-HH:MM:SS.SSS' reasoning='the logic connecting the form section requirements to the supporting source citation' />\n\u2022 These citations are displayed in-line or in the relevant list item, and are not grouped at the end of the document.\n";
|
|
3
3
|
export interface WrapSystemPromptOptions {
|
|
4
4
|
/** The original system prompt to wrap with citation instructions */
|
|
5
5
|
systemPrompt: string;
|
|
@@ -77,7 +77,7 @@ export declare function wrapCitationPrompt(options: WrapCitationPromptOptions):
|
|
|
77
77
|
export declare const CITATION_JSON_OUTPUT_FORMAT: {
|
|
78
78
|
type: string;
|
|
79
79
|
properties: {
|
|
80
|
-
|
|
80
|
+
attachmentId: {
|
|
81
81
|
type: string;
|
|
82
82
|
};
|
|
83
83
|
startPageKey: {
|
|
@@ -109,7 +109,7 @@ export declare const CITATION_JSON_OUTPUT_FORMAT: {
|
|
|
109
109
|
export declare const CITATION_AV_BASED_JSON_OUTPUT_FORMAT: {
|
|
110
110
|
type: string;
|
|
111
111
|
properties: {
|
|
112
|
-
|
|
112
|
+
attachmentId: {
|
|
113
113
|
type: string;
|
|
114
114
|
};
|
|
115
115
|
startPageKey: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const CITATION_MARKDOWN_SYNTAX_PROMPT = `
|
|
2
2
|
Citation syntax to use within Markdown:
|
|
3
3
|
• To support any ideas or information that requires a citation from the provided content, use the following citation syntax:
|
|
4
|
-
<cite file_id='
|
|
4
|
+
<cite file_id='attachment_id' start_page_key='page_number_PAGE_index_INDEX' full_phrase='the verbatim text of the terse phrase inside <file_text />; remember to escape quotes and newlines inside the full_phrase to remain as valid JSON' key_span='the verbatim 1-3 words within full_phrase that best support the citation' line_ids='2-6' reasoning='the terse logic used to conclude the citation' />
|
|
5
5
|
|
|
6
6
|
• Very important: for page numbers, only use the page number and page index info from the page_number_PAGE_index_INDEX format (e.g. <page_number_1_index_0>) and never from the contents inside the page.
|
|
7
7
|
• start_page_key, full_phrase, and line_ids are required for each citation.
|
|
@@ -13,7 +13,7 @@ Citation syntax to use within Markdown:
|
|
|
13
13
|
`;
|
|
14
14
|
export const AV_CITATION_MARKDOWN_SYNTAX_PROMPT = `
|
|
15
15
|
• To support any ideas or information that requires a citation from the provided content, use the following citation syntax:
|
|
16
|
-
<cite file_id='
|
|
16
|
+
<cite file_id='attachment_id' full_phrase='the verbatim text of the phrase; remember to escape quotes and newlines inside the full_phrase to remain as valid JSON' timestamps='HH:MM:SS.SSS-HH:MM:SS.SSS' reasoning='the logic connecting the form section requirements to the supporting source citation' />
|
|
17
17
|
• These citations are displayed in-line or in the relevant list item, and are not grouped at the end of the document.
|
|
18
18
|
`;
|
|
19
19
|
/**
|
|
@@ -110,7 +110,7 @@ export function wrapCitationPrompt(options) {
|
|
|
110
110
|
export const CITATION_JSON_OUTPUT_FORMAT = {
|
|
111
111
|
type: "object",
|
|
112
112
|
properties: {
|
|
113
|
-
|
|
113
|
+
attachmentId: { type: "string" },
|
|
114
114
|
startPageKey: {
|
|
115
115
|
type: "string",
|
|
116
116
|
description: 'Only return a result like "page_number_PAGE_index_INDEX" from the provided page keys (e.g. <page_number_1_index_0>) and never from the contents inside the page.',
|
|
@@ -134,7 +134,7 @@ export const CITATION_JSON_OUTPUT_FORMAT = {
|
|
|
134
134
|
},
|
|
135
135
|
},
|
|
136
136
|
required: [
|
|
137
|
-
"
|
|
137
|
+
"attachmentId",
|
|
138
138
|
"startPageKey",
|
|
139
139
|
"reasoning",
|
|
140
140
|
"fullPhrase",
|
|
@@ -145,7 +145,7 @@ export const CITATION_JSON_OUTPUT_FORMAT = {
|
|
|
145
145
|
export const CITATION_AV_BASED_JSON_OUTPUT_FORMAT = {
|
|
146
146
|
type: "object",
|
|
147
147
|
properties: {
|
|
148
|
-
|
|
148
|
+
attachmentId: { type: "string" },
|
|
149
149
|
startPageKey: {
|
|
150
150
|
type: "string",
|
|
151
151
|
description: 'Only return a result like "page_number_PAGE_index_INDEX" from the provided page keys (e.g. <page_number_1_index_0>) and never from the contents inside the page.',
|
|
@@ -91,7 +91,7 @@ export function decompressPromptIds(compressed, prefixMap) {
|
|
|
91
91
|
const escPrefix = prefix.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
92
92
|
text = text.replace(new RegExp(escPrefix, "g"), full);
|
|
93
93
|
}
|
|
94
|
-
//this is for citation
|
|
94
|
+
//this is for citation attachmentId, file_id, or fileId
|
|
95
95
|
if (entries.length === 1 && (text.includes("file_id='") || text.includes('file_id="'))) {
|
|
96
96
|
const fullId = entries[0][1];
|
|
97
97
|
text = text.replace(/file_id='[^']*'|file_id="[^"]*"/g, `file_id='${fullId}'`);
|
|
@@ -100,6 +100,10 @@ export function decompressPromptIds(compressed, prefixMap) {
|
|
|
100
100
|
const fullId = entries[0][1];
|
|
101
101
|
text = text.replace(/fileId='[^']*'|fileId="[^"]*"/g, `fileId='${fullId}'`);
|
|
102
102
|
}
|
|
103
|
+
else if (entries.length === 1 && (text.includes("attachmentId='") || text.includes('attachmentId="'))) {
|
|
104
|
+
const fullId = entries[0][1];
|
|
105
|
+
text = text.replace(/attachmentId='[^']*'|attachmentId="[^"]*"/g, `attachmentId='${fullId}'`);
|
|
106
|
+
}
|
|
103
107
|
const newLength = text?.length;
|
|
104
108
|
const diff = originalLength - newLength;
|
|
105
109
|
if (diff > 0) {
|
package/lib/react/styles.css
CHANGED
|
@@ -79,7 +79,8 @@
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
/* Status-specific styles */
|
|
82
|
-
|
|
82
|
+
/* Only brackets variant gets verified blue text color */
|
|
83
|
+
.dc-citation--brackets.dc-citation--verified .dc-citation-text {
|
|
83
84
|
color: var(--dc-color-verified, #2563eb);
|
|
84
85
|
}
|
|
85
86
|
|
|
@@ -87,7 +88,8 @@
|
|
|
87
88
|
background-color: var(--dc-verified-hover-bg, rgba(37, 99, 235, 0.08));
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
|
|
91
|
+
/* Only brackets variant gets verified blue text on hover */
|
|
92
|
+
.dc-citation--brackets.dc-citation--verified:hover .dc-citation-text {
|
|
91
93
|
text-decoration: underline;
|
|
92
94
|
color: var(--dc-color-verified-hover, #1d4ed8);
|
|
93
95
|
}
|
package/lib/react/utils.js
CHANGED
|
@@ -7,7 +7,7 @@ import { getCitationPageNumber } from "../parsing/normalizeCitation.js";
|
|
|
7
7
|
export function generateCitationKey(citation) {
|
|
8
8
|
const pageNumber = citation.pageNumber || getCitationPageNumber(citation.startPageKey);
|
|
9
9
|
const keyParts = [
|
|
10
|
-
citation.
|
|
10
|
+
citation.attachmentId || "",
|
|
11
11
|
pageNumber?.toString() || "",
|
|
12
12
|
citation.fullPhrase || "",
|
|
13
13
|
citation.keySpan?.toString() || "",
|
package/lib/types/citation.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export interface VerifyCitationResponse {
|
|
|
8
8
|
};
|
|
9
9
|
}
|
|
10
10
|
export interface VerifyCitationRequest {
|
|
11
|
-
|
|
11
|
+
attachmentId: string;
|
|
12
12
|
citations: {
|
|
13
13
|
[key: string]: Citation;
|
|
14
14
|
};
|
|
@@ -16,7 +16,7 @@ export interface VerifyCitationRequest {
|
|
|
16
16
|
apiKey?: string;
|
|
17
17
|
}
|
|
18
18
|
export interface Citation {
|
|
19
|
-
|
|
19
|
+
attachmentId?: string;
|
|
20
20
|
fullPhrase?: string | null;
|
|
21
21
|
keySpan?: string | null;
|
|
22
22
|
startPageKey?: string | null;
|
|
@@ -6,7 +6,7 @@ export declare const PENDING_VERIFICATION_INDEX = -2;
|
|
|
6
6
|
export declare const BLANK_VERIFICATION: Verification;
|
|
7
7
|
export declare function deterministicIdFromVerification(verification: Verification): string;
|
|
8
8
|
export interface Verification {
|
|
9
|
-
|
|
9
|
+
attachmentId?: string | null;
|
|
10
10
|
label?: string | null;
|
|
11
11
|
pageNumber?: number | null;
|
|
12
12
|
timestamp?: number | null;
|
|
@@ -2,12 +2,12 @@ import { sha1Hash } from "../utils/sha.js";
|
|
|
2
2
|
export const NOT_FOUND_VERIFICATION_INDEX = -1;
|
|
3
3
|
export const PENDING_VERIFICATION_INDEX = -2;
|
|
4
4
|
export const BLANK_VERIFICATION = {
|
|
5
|
-
|
|
5
|
+
attachmentId: null,
|
|
6
6
|
pageNumber: NOT_FOUND_VERIFICATION_INDEX,
|
|
7
7
|
matchSnippet: null,
|
|
8
8
|
source: null,
|
|
9
9
|
citation: {
|
|
10
|
-
|
|
10
|
+
attachmentId: undefined,
|
|
11
11
|
startPageKey: null,
|
|
12
12
|
fullPhrase: null,
|
|
13
13
|
keySpan: null,
|
|
@@ -17,5 +17,5 @@ export const BLANK_VERIFICATION = {
|
|
|
17
17
|
},
|
|
18
18
|
};
|
|
19
19
|
export function deterministicIdFromVerification(verification) {
|
|
20
|
-
return sha1Hash(`${verification.label}-${verification.
|
|
20
|
+
return sha1Hash(`${verification.label}-${verification.attachmentId}-${verification.pageNumber}-${verification.hitIndexWithinPage}-${verification.matchSnippet}-${verification?.hitIndexWithinPage}`);
|
|
21
21
|
}
|