@deepcitation/deepcitation-js 1.0.5 → 1.0.6
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 +4 -2
- package/lib/client/DeepCitation.js +55 -123
- package/lib/client/index.d.ts +1 -1
- package/lib/client/types.d.ts +2 -13
- package/lib/index.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ Get a free API key at [deepcitation.com](https://deepcitation.com/signup) — no
|
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
43
|
# .env
|
|
44
|
-
DEEPCITATION_API_KEY=
|
|
44
|
+
DEEPCITATION_API_KEY=sk-dc-your_api_key_here
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
---
|
|
@@ -123,7 +123,7 @@ function Response({ citations, verifications }) {
|
|
|
123
123
|
|
|
124
124
|
```typescript
|
|
125
125
|
const dc = new DeepCitation({
|
|
126
|
-
apiKey: string, // Your API key (
|
|
126
|
+
apiKey: string, // Your API key (sk-dc-*)
|
|
127
127
|
apiUrl?: string, // Optional: Custom API URL
|
|
128
128
|
});
|
|
129
129
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Citation } from "../types/index";
|
|
2
|
-
import type { CitationInput, ConvertFileInput, ConvertFileResponse, DeepCitationConfig, FileInput, PrepareConvertedFileOptions, PrepareFilesResult, UploadFileOptions, UploadFileResponse,
|
|
2
|
+
import type { CitationInput, ConvertFileInput, ConvertFileResponse, DeepCitationConfig, FileInput, PrepareConvertedFileOptions, PrepareFilesResult, UploadFileOptions, UploadFileResponse, VerifyCitationsFromLlmOutput, VerifyCitationsOptions, VerifyCitationsResponse } from "./types";
|
|
3
3
|
/**
|
|
4
4
|
* DeepCitation client for file upload and citation verification.
|
|
5
5
|
*
|
|
@@ -33,6 +33,8 @@ export declare class DeepCitation {
|
|
|
33
33
|
* This allows users to reference files by their own IDs
|
|
34
34
|
*/
|
|
35
35
|
private fileIdMap;
|
|
36
|
+
/** Store file mapping and return public response */
|
|
37
|
+
private storeAndReturnResponse;
|
|
36
38
|
/**
|
|
37
39
|
* Create a new DeepCitation client instance.
|
|
38
40
|
*
|
|
@@ -185,7 +187,7 @@ export declare class DeepCitation {
|
|
|
185
187
|
* }
|
|
186
188
|
* ```
|
|
187
189
|
*/
|
|
188
|
-
verifyCitationsFromLlmOutput(input:
|
|
190
|
+
verifyCitationsFromLlmOutput(input: VerifyCitationsFromLlmOutput, citations?: {
|
|
189
191
|
[key: string]: Citation;
|
|
190
192
|
}): Promise<VerifyCitationsResponse>;
|
|
191
193
|
/**
|
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import { getAllCitationsFromLlmOutput } from "../parsing/parseCitation";
|
|
2
2
|
import { generateCitationKey } from "../react/utils";
|
|
3
3
|
const DEFAULT_API_URL = "https://api.deepcitation.com";
|
|
4
|
+
/** Convert File/Blob/Buffer to a Blob suitable for FormData */
|
|
5
|
+
function toBlob(file, filename) {
|
|
6
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(file)) {
|
|
7
|
+
const uint8 = Uint8Array.from(file);
|
|
8
|
+
return { blob: new Blob([uint8]), name: filename || "document" };
|
|
9
|
+
}
|
|
10
|
+
if (file instanceof Blob) {
|
|
11
|
+
return {
|
|
12
|
+
blob: file,
|
|
13
|
+
name: filename || (file instanceof File ? file.name : "document"),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
throw new Error("Invalid file type. Expected File, Blob, or Buffer.");
|
|
17
|
+
}
|
|
18
|
+
/** Extract error message from API response */
|
|
19
|
+
async function extractErrorMessage(response, fallbackAction) {
|
|
20
|
+
const error = await response.json().catch(() => ({}));
|
|
21
|
+
return error?.error?.message || `${fallbackAction} failed with status ${response.status}`;
|
|
22
|
+
}
|
|
4
23
|
/**
|
|
5
24
|
* DeepCitation client for file upload and citation verification.
|
|
6
25
|
*
|
|
@@ -34,6 +53,12 @@ export class DeepCitation {
|
|
|
34
53
|
* This allows users to reference files by their own IDs
|
|
35
54
|
*/
|
|
36
55
|
fileIdMap = new Map();
|
|
56
|
+
/** Store file mapping and return public response */
|
|
57
|
+
storeAndReturnResponse(apiResponse) {
|
|
58
|
+
this.fileIdMap.set(apiResponse.fileId, { attachmentId: apiResponse.attachmentId });
|
|
59
|
+
const { attachmentId: _, ...publicResponse } = apiResponse;
|
|
60
|
+
return publicResponse;
|
|
61
|
+
}
|
|
37
62
|
/**
|
|
38
63
|
* Create a new DeepCitation client instance.
|
|
39
64
|
*
|
|
@@ -71,51 +96,22 @@ export class DeepCitation {
|
|
|
71
96
|
* ```
|
|
72
97
|
*/
|
|
73
98
|
async uploadFile(file, options) {
|
|
99
|
+
const { blob, name } = toBlob(file, options?.filename);
|
|
74
100
|
const formData = new FormData();
|
|
75
|
-
|
|
76
|
-
if (
|
|
77
|
-
// Node.js Buffer - copy to a new ArrayBuffer for Blob compatibility
|
|
78
|
-
const filename = options?.filename || "document";
|
|
79
|
-
// Use Uint8Array.from to create a copy that's definitely backed by ArrayBuffer (not SharedArrayBuffer)
|
|
80
|
-
const uint8 = Uint8Array.from(file);
|
|
81
|
-
const blob = new Blob([uint8]);
|
|
82
|
-
formData.append("file", blob, filename);
|
|
83
|
-
}
|
|
84
|
-
else if (file instanceof Blob) {
|
|
85
|
-
// File or Blob
|
|
86
|
-
const filename = options?.filename || (file instanceof File ? file.name : "document");
|
|
87
|
-
formData.append("file", file, filename);
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
throw new Error("Invalid file type. Expected File, Blob, or Buffer.");
|
|
91
|
-
}
|
|
92
|
-
// Add optional fields
|
|
93
|
-
if (options?.fileId) {
|
|
101
|
+
formData.append("file", blob, name);
|
|
102
|
+
if (options?.fileId)
|
|
94
103
|
formData.append("fileId", options.fileId);
|
|
95
|
-
|
|
96
|
-
if (options?.filename) {
|
|
104
|
+
if (options?.filename)
|
|
97
105
|
formData.append("filename", options.filename);
|
|
98
|
-
}
|
|
99
106
|
const response = await fetch(`${this.apiUrl}/prepareFile`, {
|
|
100
107
|
method: "POST",
|
|
101
|
-
headers: {
|
|
102
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
103
|
-
},
|
|
108
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
104
109
|
body: formData,
|
|
105
110
|
});
|
|
106
111
|
if (!response.ok) {
|
|
107
|
-
|
|
108
|
-
throw new Error(error?.error?.message || `Upload failed with status ${response.status}`);
|
|
112
|
+
throw new Error(await extractErrorMessage(response, "Upload"));
|
|
109
113
|
}
|
|
110
|
-
|
|
111
|
-
const apiResponse = (await response.json());
|
|
112
|
-
// Store the mapping for later verification calls
|
|
113
|
-
this.fileIdMap.set(apiResponse.fileId, {
|
|
114
|
-
attachmentId: apiResponse.attachmentId,
|
|
115
|
-
});
|
|
116
|
-
// Return public response without internal fields
|
|
117
|
-
const { attachmentId: _attachmentId, ...publicResponse } = apiResponse;
|
|
118
|
-
return publicResponse;
|
|
114
|
+
return this.storeAndReturnResponse(await response.json());
|
|
119
115
|
}
|
|
120
116
|
/**
|
|
121
117
|
* Convert a URL or Office file to PDF for citation verification.
|
|
@@ -150,75 +146,42 @@ export class DeepCitation {
|
|
|
150
146
|
* ```
|
|
151
147
|
*/
|
|
152
148
|
async convertToPdf(input) {
|
|
153
|
-
// Handle string URL shorthand
|
|
154
149
|
const inputObj = typeof input === "string" ? { url: input } : input;
|
|
155
|
-
const { url, file, filename, fileId
|
|
150
|
+
const { url, file, filename, fileId } = inputObj;
|
|
156
151
|
if (!url && !file) {
|
|
157
152
|
throw new Error("Either url or file must be provided");
|
|
158
153
|
}
|
|
159
154
|
let response;
|
|
160
155
|
if (url) {
|
|
161
|
-
// URL conversion - send as JSON
|
|
162
156
|
response = await fetch(`${this.apiUrl}/convertFile`, {
|
|
163
157
|
method: "POST",
|
|
164
158
|
headers: {
|
|
165
159
|
Authorization: `Bearer ${this.apiKey}`,
|
|
166
160
|
"Content-Type": "application/json",
|
|
167
161
|
},
|
|
168
|
-
body: JSON.stringify({
|
|
169
|
-
url,
|
|
170
|
-
filename,
|
|
171
|
-
fileId,
|
|
172
|
-
singlePage,
|
|
173
|
-
}),
|
|
162
|
+
body: JSON.stringify({ url, filename, fileId }),
|
|
174
163
|
});
|
|
175
164
|
}
|
|
176
|
-
else
|
|
177
|
-
|
|
165
|
+
else {
|
|
166
|
+
const { blob, name } = toBlob(file, filename);
|
|
178
167
|
const formData = new FormData();
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const uint8 = Uint8Array.from(file);
|
|
182
|
-
const blob = new Blob([uint8]);
|
|
183
|
-
formData.append("file", blob, fname);
|
|
184
|
-
}
|
|
185
|
-
else if (file instanceof Blob) {
|
|
186
|
-
const fname = filename || (file instanceof File ? file.name : "document");
|
|
187
|
-
formData.append("file", file, fname);
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
throw new Error("Invalid file type. Expected File, Blob, or Buffer.");
|
|
191
|
-
}
|
|
192
|
-
if (fileId) {
|
|
168
|
+
formData.append("file", blob, name);
|
|
169
|
+
if (fileId)
|
|
193
170
|
formData.append("fileId", fileId);
|
|
194
|
-
|
|
195
|
-
if (filename) {
|
|
171
|
+
if (filename)
|
|
196
172
|
formData.append("filename", filename);
|
|
197
|
-
}
|
|
198
173
|
response = await fetch(`${this.apiUrl}/convertFile`, {
|
|
199
174
|
method: "POST",
|
|
200
|
-
headers: {
|
|
201
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
202
|
-
},
|
|
175
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
203
176
|
body: formData,
|
|
204
177
|
});
|
|
205
178
|
}
|
|
206
|
-
else {
|
|
207
|
-
throw new Error("Either url or file must be provided");
|
|
208
|
-
}
|
|
209
179
|
if (!response.ok) {
|
|
210
|
-
|
|
211
|
-
throw new Error(error?.error?.message ||
|
|
212
|
-
`Conversion failed with status ${response.status}`);
|
|
180
|
+
throw new Error(await extractErrorMessage(response, "Conversion"));
|
|
213
181
|
}
|
|
214
|
-
// Internal response includes attachmentId which we need for the two-step flow
|
|
215
182
|
const apiResponse = (await response.json());
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
attachmentId: apiResponse.attachmentId,
|
|
219
|
-
});
|
|
220
|
-
// Return public response without internal fields
|
|
221
|
-
const { attachmentId: _attachmentId, ...publicResponse } = apiResponse;
|
|
183
|
+
this.fileIdMap.set(apiResponse.fileId, { attachmentId: apiResponse.attachmentId });
|
|
184
|
+
const { attachmentId: _, ...publicResponse } = apiResponse;
|
|
222
185
|
return publicResponse;
|
|
223
186
|
}
|
|
224
187
|
/**
|
|
@@ -242,7 +205,6 @@ export class DeepCitation {
|
|
|
242
205
|
* ```
|
|
243
206
|
*/
|
|
244
207
|
async prepareConvertedFile(options) {
|
|
245
|
-
// Look up the internal attachmentId from the fileId
|
|
246
208
|
const fileInfo = this.fileIdMap.get(options.fileId);
|
|
247
209
|
if (!fileInfo) {
|
|
248
210
|
throw new Error(`File ID "${options.fileId}" not found. Make sure to call convertToPdf() first.`);
|
|
@@ -259,18 +221,9 @@ export class DeepCitation {
|
|
|
259
221
|
}),
|
|
260
222
|
});
|
|
261
223
|
if (!response.ok) {
|
|
262
|
-
|
|
263
|
-
throw new Error(error?.error?.message || `Prepare failed with status ${response.status}`);
|
|
224
|
+
throw new Error(await extractErrorMessage(response, "Prepare"));
|
|
264
225
|
}
|
|
265
|
-
|
|
266
|
-
const apiResponse = (await response.json());
|
|
267
|
-
// Update the mapping (attachmentId should remain the same)
|
|
268
|
-
this.fileIdMap.set(apiResponse.fileId, {
|
|
269
|
-
attachmentId: apiResponse.attachmentId,
|
|
270
|
-
});
|
|
271
|
-
// Return public response without internal fields
|
|
272
|
-
const { attachmentId: _attachmentId, ...publicResponse } = apiResponse;
|
|
273
|
-
return publicResponse;
|
|
226
|
+
return this.storeAndReturnResponse(await response.json());
|
|
274
227
|
}
|
|
275
228
|
/**
|
|
276
229
|
* Upload multiple files for citation verification and get structured content.
|
|
@@ -377,9 +330,7 @@ export class DeepCitation {
|
|
|
377
330
|
}),
|
|
378
331
|
});
|
|
379
332
|
if (!response.ok) {
|
|
380
|
-
|
|
381
|
-
throw new Error(error?.error?.message ||
|
|
382
|
-
`Verification failed with status ${response.status}`);
|
|
333
|
+
throw new Error(await extractErrorMessage(response, "Verification"));
|
|
383
334
|
}
|
|
384
335
|
return (await response.json());
|
|
385
336
|
}
|
|
@@ -414,7 +365,7 @@ export class DeepCitation {
|
|
|
414
365
|
// Note: fileDataParts is now only used to identify which files to verify
|
|
415
366
|
// The mapping from fileId to attachmentId must be registered via uploadFile() or prepareFiles()
|
|
416
367
|
// in the same session. For Zero Data Retention scenarios, use verifyCitations() directly.
|
|
417
|
-
// Group citations by fileId
|
|
368
|
+
// Group citations by fileId
|
|
418
369
|
const citationsByFile = new Map();
|
|
419
370
|
for (const [key, citation] of Object.entries(citations)) {
|
|
420
371
|
const fileId = citation.fileId || "";
|
|
@@ -423,35 +374,16 @@ export class DeepCitation {
|
|
|
423
374
|
}
|
|
424
375
|
citationsByFile.get(fileId)[key] = citation;
|
|
425
376
|
}
|
|
426
|
-
//
|
|
427
|
-
const
|
|
377
|
+
// Filter to only registered files and verify in parallel
|
|
378
|
+
const verificationPromises = [];
|
|
428
379
|
for (const [fileId, fileCitations] of citationsByFile) {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
if (!fileInfo) {
|
|
432
|
-
// Skip citations for unregistered files
|
|
433
|
-
continue;
|
|
380
|
+
if (this.fileIdMap.has(fileId)) {
|
|
381
|
+
verificationPromises.push(this.verifyCitations(fileId, fileCitations, { outputImageFormat }));
|
|
434
382
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
"Content-Type": "application/json",
|
|
440
|
-
},
|
|
441
|
-
body: JSON.stringify({
|
|
442
|
-
data: {
|
|
443
|
-
attachmentId: fileInfo.attachmentId,
|
|
444
|
-
citations: fileCitations,
|
|
445
|
-
outputImageFormat,
|
|
446
|
-
},
|
|
447
|
-
}),
|
|
448
|
-
});
|
|
449
|
-
if (!response.ok) {
|
|
450
|
-
const error = await response.json().catch(() => ({}));
|
|
451
|
-
throw new Error(error?.error?.message ||
|
|
452
|
-
`Verification failed with status ${response.status}`);
|
|
453
|
-
}
|
|
454
|
-
const result = (await response.json());
|
|
383
|
+
}
|
|
384
|
+
const results = await Promise.all(verificationPromises);
|
|
385
|
+
const allHighlights = {};
|
|
386
|
+
for (const result of results) {
|
|
455
387
|
Object.assign(allHighlights, result.foundHighlights);
|
|
456
388
|
}
|
|
457
389
|
return { foundHighlights: allHighlights };
|
package/lib/client/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { DeepCitation } from "./DeepCitation";
|
|
2
|
-
export type { DeepCitationConfig, UploadFileResponse, UploadFileOptions, VerifyCitationsResponse, VerifyCitationsOptions, CitationInput, FileInput, FileDataPart, PrepareFilesResult,
|
|
2
|
+
export type { DeepCitationConfig, UploadFileResponse, UploadFileOptions, VerifyCitationsResponse, VerifyCitationsOptions, CitationInput, FileInput, FileDataPart, PrepareFilesResult, VerifyCitationsFromLlmOutput, ConvertFileInput, ConvertFileResponse, PrepareConvertedFileOptions, } from "./types";
|
package/lib/client/types.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { Citation, FoundHighlightLocation } from "../types/index";
|
|
|
3
3
|
* Configuration options for the DeepCitation client
|
|
4
4
|
*/
|
|
5
5
|
export interface DeepCitationConfig {
|
|
6
|
-
/** Your DeepCitation API key (starts with
|
|
6
|
+
/** Your DeepCitation API key (starts with sk-dc-) */
|
|
7
7
|
apiKey: string;
|
|
8
8
|
/** Optional custom API base URL. Defaults to https://api.deepcitation.com */
|
|
9
9
|
apiUrl?: string;
|
|
@@ -94,7 +94,7 @@ export interface PrepareFilesResult {
|
|
|
94
94
|
/**
|
|
95
95
|
* Input for verifyCitationsFromLlmOutput
|
|
96
96
|
*/
|
|
97
|
-
export interface
|
|
97
|
+
export interface VerifyCitationsFromLlmOutput {
|
|
98
98
|
/** The LLM response containing citations */
|
|
99
99
|
llmOutput: string;
|
|
100
100
|
/** Optional file references (required for Zero Data Retention or after storage expires) */
|
|
@@ -114,8 +114,6 @@ export interface ConvertFileInput {
|
|
|
114
114
|
filename?: string;
|
|
115
115
|
/** Optional custom file ID */
|
|
116
116
|
fileId?: string;
|
|
117
|
-
/** For URLs: render as single long page instead of paginated */
|
|
118
|
-
singlePage?: boolean;
|
|
119
117
|
}
|
|
120
118
|
/**
|
|
121
119
|
* Response from convertFile
|
|
@@ -146,12 +144,3 @@ export interface PrepareConvertedFileOptions {
|
|
|
146
144
|
/** The file ID from a previous convertFile call */
|
|
147
145
|
fileId: string;
|
|
148
146
|
}
|
|
149
|
-
/**
|
|
150
|
-
* @deprecated Use PrepareConvertedFileOptions instead
|
|
151
|
-
*/
|
|
152
|
-
export interface PrepareFileFromAttachmentOptions {
|
|
153
|
-
/** The attachment ID from a previous convertFile call */
|
|
154
|
-
attachmentId: string;
|
|
155
|
-
/** Optional custom file ID */
|
|
156
|
-
fileId?: string;
|
|
157
|
-
}
|
package/lib/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @packageDocumentation
|
|
4
4
|
*/
|
|
5
5
|
export { DeepCitation } from "./client/index.js";
|
|
6
|
-
export type { DeepCitationConfig, UploadFileResponse, UploadFileOptions, VerifyCitationsResponse, VerifyCitationsOptions, CitationInput, FileInput, FileDataPart, PrepareFilesResult,
|
|
6
|
+
export type { DeepCitationConfig, UploadFileResponse, UploadFileOptions, VerifyCitationsResponse, VerifyCitationsOptions, CitationInput, FileInput, FileDataPart, PrepareFilesResult, VerifyCitationsFromLlmOutput, } from "./client/index.js";
|
|
7
7
|
export { parseCitation, getCitationStatus, getAllCitationsFromLlmOutput, groupCitationsByFileId, groupCitationsByFileIdObject, } from "./parsing/parseCitation.js";
|
|
8
8
|
export { normalizeCitations, getCitationPageNumber, } from "./parsing/normalizeCitation.js";
|
|
9
9
|
export { isGeminiGarbage, cleanRepeatingLastSentence, } from "./parsing/parseWorkAround.js";
|