@saplingai/sapling-js 1.0.52 → 1.0.53
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 +73 -0
- package/package.json +1 -1
- package/sapling/client.d.ts +167 -2
- package/sapling/client.js +310 -0
package/README.md
CHANGED
|
@@ -102,6 +102,79 @@ client
|
|
|
102
102
|
})
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
+
### AI content detection
|
|
106
|
+
|
|
107
|
+
```javascript
|
|
108
|
+
const { data } = await client.aidetect(documentText);
|
|
109
|
+
// { score: 0.05, ai_fraction: 0.51, text: '...', version: '...',
|
|
110
|
+
// sentence_scores: [{ sentence: '...', score: 0.97, start: 0, end: 112 }, ...],
|
|
111
|
+
// tokens: [...], token_probs: [...] }
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`score` (0–1) rates the whole text, so a document that is only partly AI-written can score low;
|
|
115
|
+
`ai_fraction` is the share of the text in sentences scoring ≥ 0.5 (null when nothing could be
|
|
116
|
+
scored), which flags those documents. `sentence_scores` use the same model and 0.5 threshold, and
|
|
117
|
+
`start`/`end` index `text` in Unicode code points (`end` exclusive) — slice with
|
|
118
|
+
`Array.from(text).slice(start, end).join('')` if the text may contain emoji. A response may also
|
|
119
|
+
carry an advisory `language` (`{ code, name, confidence }`) when the text is confidently
|
|
120
|
+
identified; the detector is trained on English. Options: `sent_scores` (default true),
|
|
121
|
+
`score_string` (HTML heatmap), `version` (pin a model). Pass an array of 1–10 texts for the batch
|
|
122
|
+
form, which answers `{ results: [...] }` in input order.
|
|
123
|
+
|
|
124
|
+
### Rephrase, sentiment and readability statistics
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
const { data: rephrased } = await client.rephrase('hey, can u send it over', {
|
|
128
|
+
mapping: 'informal_to_formal', // default 'paraphrase'
|
|
129
|
+
num_results: 3, // 1–8, default 5
|
|
130
|
+
});
|
|
131
|
+
// { results: [{ hash: '...', original: '...', replacement: 'Could you please send it over?',
|
|
132
|
+
// rephrase_type: 'informal_to_formal', model_version: '...' }, ...] }
|
|
133
|
+
|
|
134
|
+
const { data: sentiment } = await client.sentiment('Love the new release. The install was painful.');
|
|
135
|
+
// { overall: [[0.71, 'POSITIVE'], [0.29, 'NEGATIVE']],
|
|
136
|
+
// results: [[[0.99, 'POSITIVE']], [[0.95, 'NEGATIVE'], ...]],
|
|
137
|
+
// sents: ['Love the new release.', 'The install was painful.'] }
|
|
138
|
+
|
|
139
|
+
const { data: stats } = await client.statistics(documentText, { lang: 'en' });
|
|
140
|
+
// { chars: 1234, words: 210, sentences: 12, reading_time_sec: 47.55, reading_time_min: 1,
|
|
141
|
+
// readability: 8.2, readability_scores: { flesch_reading_ease: 64.1, gunning_fog: 10.3, ... } }
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`rephrase()` mappings: `paraphrase`, `reword`, `expand`, `shorten`, `summarize`,
|
|
145
|
+
`passive_to_active`, `active_to_passive`, `informal_to_formal`, `formal_to_informal`,
|
|
146
|
+
`sentence_split`, `generate_title`, `generate_acronym`, `generate_meta_description`,
|
|
147
|
+
`switch_tone` (with `tone_mapping`) and `switch_tense` (with `tense_mapping`). Sentiment labels
|
|
148
|
+
are `POSITIVE`, `NEUTRAL` and `NEGATIVE`, as `[probability, label]` pairs, most likely first;
|
|
149
|
+
a text with no sentences (markup or punctuation only) gets a 204 with an empty body (`data === ''`),
|
|
150
|
+
and one the API cannot split into sentences gets a 200 with `[]`.
|
|
151
|
+
`statistics()` is unbilled; `lang` is one of `en`, `de`, `es`, `fr`, `it`, `nl`, `pl`, `ru`, `ar`
|
|
152
|
+
or `auto` and picks the readability formulas (`readability_scores` is empty for `auto`).
|
|
153
|
+
|
|
154
|
+
### Similar terms, profanity and the custom dictionary
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
const { data: similar } = await client.similarTerms('pet');
|
|
158
|
+
// { neighbors: [[0.81, 'dog'], [0.78, 'cat'], [0.75, 'puppy']],
|
|
159
|
+
// synonyms: ['darling', 'favorite', 'favourite', ...] }
|
|
160
|
+
|
|
161
|
+
const { data: profanity } = await client.profanity('This is so damn stupid.');
|
|
162
|
+
// { toks: ['This', 'is', 'so', 'damn', 'stupid.'], labels: [0, 0, 0, 1, 1] }
|
|
163
|
+
|
|
164
|
+
// Words the grammar and spelling checks should accept (product names, jargon).
|
|
165
|
+
const { data: entry } = await client.addDictionaryEntry('Sapling', { case_sensitive: true });
|
|
166
|
+
// { id: '...', entry: 'Sapling', case_sensitive: true, created_at: '...', updated_at: '...' }
|
|
167
|
+
const { data: entries } = await client.dictionaryEntries(); // a bare array, sorted by entry
|
|
168
|
+
await client.deleteDictionaryEntry(entry.id); // 204, empty body
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`similarTerms()` returns up to five `[similarity, term]` neighbors (similarity 0–1, most similar
|
|
172
|
+
first) plus WordNet `synonyms` (multi-word ones use underscores); only the first 100 characters of
|
|
173
|
+
the query are used. `profanity()` splits the text on whitespace and labels each token 1 (profane)
|
|
174
|
+
or 0. Both are unbilled. `addDictionaryEntry()` answers 400 for a duplicate entry or one with more
|
|
175
|
+
than two digits; `deleteDictionaryEntry()` answers 400 (not 404) for an id that is not this
|
|
176
|
+
account's.
|
|
177
|
+
|
|
105
178
|
### Style-guide rulesets
|
|
106
179
|
|
|
107
180
|
Check text against style rules inline, or save a named ruleset once (owned by
|
package/package.json
CHANGED
package/sapling/client.d.ts
CHANGED
|
@@ -38,7 +38,54 @@ export type EditOptions = {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
export type AIDetectOptions = {
|
|
41
|
+
/** Include `sentence_scores` (server default true). */
|
|
41
42
|
sent_scores?: boolean,
|
|
43
|
+
/** Include `score_string`, an HTML heatmap of the scored text. */
|
|
44
|
+
score_string?: boolean,
|
|
45
|
+
/** Pin a detector model version (e.g. `'20251027'`); omitted = current default. */
|
|
46
|
+
version?: string,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type AIDetectSentenceScore = {
|
|
50
|
+
sentence: string;
|
|
51
|
+
/** Same model, 0–1 scale and 0.5 threshold as the overall `score`. */
|
|
52
|
+
score: number;
|
|
53
|
+
/** Offsets into the response `text` (`end` exclusive), counted in Unicode
|
|
54
|
+
* code points — slice with `Array.from(text)` if it may contain emoji. */
|
|
55
|
+
start: number;
|
|
56
|
+
end: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type AIDetectLanguage = {
|
|
60
|
+
code: string;
|
|
61
|
+
/** English display name; null for codes without one. */
|
|
62
|
+
name: string | null;
|
|
63
|
+
confidence: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type AIDetectResponse = {
|
|
67
|
+
/** 0 (likely human-written) to 1 (likely AI-generated). */
|
|
68
|
+
score: number;
|
|
69
|
+
/** Share of the text in sentences scoring >= 0.5 (flags partially
|
|
70
|
+
* AI-written documents); null when nothing could be scored. */
|
|
71
|
+
ai_fraction: number | null;
|
|
72
|
+
text: string;
|
|
73
|
+
tokens: string[];
|
|
74
|
+
token_probs: number[];
|
|
75
|
+
/** The pinned `version` when one was requested, otherwise an opaque
|
|
76
|
+
* identifier of the default model — log it, don't parse it. */
|
|
77
|
+
version: string;
|
|
78
|
+
/** Present unless `sent_scores: false`. */
|
|
79
|
+
sentence_scores?: AIDetectSentenceScore[];
|
|
80
|
+
/** Present when `score_string: true`. */
|
|
81
|
+
score_string?: string;
|
|
82
|
+
/** Advisory detected language; omitted unless detection is high-confidence.
|
|
83
|
+
* The detector is trained on English. */
|
|
84
|
+
language?: AIDetectLanguage;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type AIDetectBatchResponse = {
|
|
88
|
+
results: AIDetectResponse[];
|
|
42
89
|
}
|
|
43
90
|
|
|
44
91
|
export type StyleguideRule = string | { name: string; description?: string };
|
|
@@ -274,7 +321,12 @@ export type QualityBatchResponse = {
|
|
|
274
321
|
results: QualityResponse[];
|
|
275
322
|
}
|
|
276
323
|
|
|
277
|
-
export type
|
|
324
|
+
export type PIIDeterministicType = 'email' | 'phone' | 'ssn' | 'credit_card' | 'ip_address' | 'iban' | 'us_bank_routing';
|
|
325
|
+
// Opt-in, LLM-backed named-entity PII types: detected only when listed in
|
|
326
|
+
// `types` explicitly (a request without them stays deterministic-only), and
|
|
327
|
+
// the text is then capped at 10,000 characters rather than 20,000.
|
|
328
|
+
export type PIINerType = 'person' | 'organization' | 'location';
|
|
329
|
+
export type PIIType = PIIDeterministicType | PIINerType;
|
|
278
330
|
|
|
279
331
|
export type PIIOptions = {
|
|
280
332
|
types?: PIIType[],
|
|
@@ -422,6 +474,105 @@ export type SummarizeBatchResponse = {
|
|
|
422
474
|
results: SummarizeResponse[];
|
|
423
475
|
}
|
|
424
476
|
|
|
477
|
+
export type RephraseMapping =
|
|
478
|
+
'paraphrase' | 'passive_to_active' | 'active_to_passive' | 'informal_to_formal' |
|
|
479
|
+
'formal_to_informal' | 'sentence_split' | 'reword' | 'generate_title' | 'generate_acronym' |
|
|
480
|
+
'generate_meta_description' | 'summarize' | 'expand' | 'shorten' | 'switch_tone' | 'switch_tense';
|
|
481
|
+
|
|
482
|
+
export type RephraseToneMapping =
|
|
483
|
+
'straightforward' | 'confident' | 'friendly' | 'empathetic' | 'professional' | 'casual' |
|
|
484
|
+
'diplomatic' | 'persuasive';
|
|
485
|
+
|
|
486
|
+
export type RephraseTenseMapping =
|
|
487
|
+
'simple past' | 'past perfect' | 'past continuous' | 'past perfect continuous' |
|
|
488
|
+
'simple present' | 'present perfect' | 'present continuous' | 'present perfect continuous' |
|
|
489
|
+
'simple future' | 'future perfect' | 'future continuous' | 'future perfect continuous';
|
|
490
|
+
|
|
491
|
+
export type RephraseOptions = {
|
|
492
|
+
/** Defaults to 'paraphrase'. */
|
|
493
|
+
mapping?: RephraseMapping,
|
|
494
|
+
/** Only used with mapping 'switch_tone'. Defaults to 'straightforward'. */
|
|
495
|
+
tone_mapping?: RephraseToneMapping,
|
|
496
|
+
/** Only used with mapping 'switch_tense'. Defaults to 'simple past'. */
|
|
497
|
+
tense_mapping?: RephraseTenseMapping,
|
|
498
|
+
/** Number of alternatives, 1-8 (default 5). Honored by paraphrase, informal_to_formal,
|
|
499
|
+
* formal_to_informal, generate_title and generate_acronym. */
|
|
500
|
+
num_results?: number,
|
|
501
|
+
session_id?: string,
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
export type RephraseResult = {
|
|
505
|
+
hash: string,
|
|
506
|
+
original: string,
|
|
507
|
+
replacement: string,
|
|
508
|
+
rephrase_type: RephraseMapping,
|
|
509
|
+
model_version: string,
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
export type RephraseResponse = {
|
|
513
|
+
results: RephraseResult[],
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
export type SentimentLabel = 'POSITIVE' | 'NEUTRAL' | 'NEGATIVE';
|
|
517
|
+
|
|
518
|
+
/** [probability, label] pairs, most likely first. */
|
|
519
|
+
export type SentimentScores = Array<[number, SentimentLabel]>;
|
|
520
|
+
|
|
521
|
+
export type SentimentResponse = {
|
|
522
|
+
overall: SentimentScores,
|
|
523
|
+
/** One entry per sentence, aligned with `sents`. */
|
|
524
|
+
results: SentimentScores[],
|
|
525
|
+
sents: string[],
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
export type StatisticsLang = 'en' | 'de' | 'es' | 'fr' | 'it' | 'nl' | 'pl' | 'ru' | 'ar' | 'auto';
|
|
529
|
+
|
|
530
|
+
export type StatisticsOptions = {
|
|
531
|
+
/** Defaults to 'en'. */
|
|
532
|
+
lang?: StatisticsLang,
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
export type StatisticsResponse = {
|
|
536
|
+
chars: number,
|
|
537
|
+
words: number,
|
|
538
|
+
sentences: number,
|
|
539
|
+
reading_time_sec: number,
|
|
540
|
+
reading_time_min: number,
|
|
541
|
+
/** Flesch-Kincaid grade level; -3.4 when the text cannot be scored. */
|
|
542
|
+
readability: number,
|
|
543
|
+
/** Formula name -> score. The formulas present depend on `lang`; empty for 'auto'.
|
|
544
|
+
* `text_standard` is a string (e.g. "8th and 9th grade"). */
|
|
545
|
+
readability_scores: Record<string, number | string>,
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
export type SimilarTermsResponse = {
|
|
549
|
+
/** Up to 5 [similarity, term] pairs (similarity 0-1), most similar first. */
|
|
550
|
+
neighbors: Array<[number, string]>,
|
|
551
|
+
/** WordNet lemma names; multi-word ones use underscores (e.g. "domestic_dog"). */
|
|
552
|
+
synonyms: string[],
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
export type ProfanityResponse = {
|
|
556
|
+
/** The text split on whitespace (punctuation stays attached). */
|
|
557
|
+
toks: string[],
|
|
558
|
+
/** Aligned with `toks`: 1 for profanity, 0 otherwise. */
|
|
559
|
+
labels: Array<0 | 1>,
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
export type DictionaryEntry = {
|
|
563
|
+
id: string,
|
|
564
|
+
entry: string,
|
|
565
|
+
case_sensitive: boolean,
|
|
566
|
+
/** RFC 1123 timestamp, e.g. "Wed, 07 Oct 2020 21:15:47 GMT". */
|
|
567
|
+
created_at: string,
|
|
568
|
+
updated_at: string,
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
export type AddDictionaryEntryOptions = {
|
|
572
|
+
/** Defaults to true. */
|
|
573
|
+
case_sensitive?: boolean,
|
|
574
|
+
}
|
|
575
|
+
|
|
425
576
|
export type UserDetails = {
|
|
426
577
|
userId?: string,
|
|
427
578
|
anonUser?: boolean,
|
|
@@ -439,6 +590,18 @@ export declare class Client {
|
|
|
439
590
|
complete(query: string, sessionId?: string): Promise<AxiosResponse>;
|
|
440
591
|
acceptComplete(completeUUID: string, query: string, completion: string, sessionId?: string): Promise<AxiosResponse>;
|
|
441
592
|
tone(text: string): Promise<AxiosResponse>;
|
|
593
|
+
rephrase(text: string, options?: RephraseOptions): Promise<AxiosResponse<RephraseResponse>>;
|
|
594
|
+
/** A text with no sentences (e.g. markup or punctuation only) gets a 204 with an empty
|
|
595
|
+
* body, so `data` is `''` then; a text the API cannot split into sentences gets a 200
|
|
596
|
+
* with `[]`. Check `Array.isArray(data) || !data` before reading fields. */
|
|
597
|
+
sentiment(text: string): Promise<AxiosResponse<SentimentResponse | '' | never[]>>;
|
|
598
|
+
statistics(text: string, options?: StatisticsOptions): Promise<AxiosResponse<StatisticsResponse>>;
|
|
599
|
+
similarTerms(query: string): Promise<AxiosResponse<SimilarTermsResponse>>;
|
|
600
|
+
profanity(text: string): Promise<AxiosResponse<ProfanityResponse>>;
|
|
601
|
+
dictionaryEntries(): Promise<AxiosResponse<DictionaryEntry[]>>;
|
|
602
|
+
addDictionaryEntry(entry: string, options?: AddDictionaryEntryOptions): Promise<AxiosResponse<DictionaryEntry>>;
|
|
603
|
+
/** Answers 204 with an empty body. */
|
|
604
|
+
deleteDictionaryEntry(entryId: string): Promise<AxiosResponse<''>>;
|
|
442
605
|
extract(text: string, fields: ExtractField[], options?: ExtractOptions): Promise<AxiosResponse<ExtractResponse>>;
|
|
443
606
|
extract(texts: string[], fields: ExtractField[], options?: ExtractOptions): Promise<AxiosResponse<ExtractBatchResponse>>;
|
|
444
607
|
extract(text: string | string[], fields: ExtractField[], options?: ExtractOptions): Promise<AxiosResponse<ExtractResponse | ExtractBatchResponse>>;
|
|
@@ -449,7 +612,9 @@ export declare class Client {
|
|
|
449
612
|
summarize(texts: string[], options?: SummarizeOptions): Promise<AxiosResponse<SummarizeBatchResponse>>;
|
|
450
613
|
summarize(text: string | string[], options?: SummarizeOptions): Promise<AxiosResponse<SummarizeResponse | SummarizeBatchResponse>>;
|
|
451
614
|
|
|
452
|
-
aidetect(text: string
|
|
615
|
+
aidetect(text: string, options?: AIDetectOptions): Promise<AxiosResponse<AIDetectResponse>>;
|
|
616
|
+
aidetect(texts: string[], options?: AIDetectOptions): Promise<AxiosResponse<AIDetectBatchResponse>>;
|
|
617
|
+
aidetect(text: string | string[], options?: AIDetectOptions): Promise<AxiosResponse<AIDetectResponse | AIDetectBatchResponse>>;
|
|
453
618
|
styleguide(text: string, rules: StyleguideRule[] | string): Promise<AxiosResponse<StyleguideResponse>>;
|
|
454
619
|
styleguide(texts: string[], rules: StyleguideRule[] | string): Promise<AxiosResponse<StyleguideBatchResponse>>;
|
|
455
620
|
styleguide(text: string | string[], rules: StyleguideRule[] | string): Promise<AxiosResponse<StyleguideResponse | StyleguideBatchResponse>>;
|
package/sapling/client.js
CHANGED
|
@@ -8,6 +8,24 @@ const TRANSLATE_FORMALITIES = ['formal', 'informal'];
|
|
|
8
8
|
const SUMMARIZE_LENGTHS = ['short', 'medium', 'long'];
|
|
9
9
|
// Reading levels simplify() accepts; the API rejects anything else with a 400.
|
|
10
10
|
const SIMPLIFY_READING_LEVELS = ['plain', 'elementary', 'middle_school', 'high_school'];
|
|
11
|
+
// rephrase() enums, mirroring the API's validators (utils/request_validator.py),
|
|
12
|
+
// which reject anything else with a 400.
|
|
13
|
+
const REPHRASE_MAPPINGS = [
|
|
14
|
+
'paraphrase', 'passive_to_active', 'active_to_passive', 'informal_to_formal',
|
|
15
|
+
'formal_to_informal', 'sentence_split', 'reword', 'generate_title', 'generate_acronym',
|
|
16
|
+
'generate_meta_description', 'summarize', 'expand', 'shorten', 'switch_tone', 'switch_tense',
|
|
17
|
+
];
|
|
18
|
+
const REPHRASE_TONE_MAPPINGS = [
|
|
19
|
+
'straightforward', 'confident', 'friendly', 'empathetic', 'professional', 'casual',
|
|
20
|
+
'diplomatic', 'persuasive',
|
|
21
|
+
];
|
|
22
|
+
const REPHRASE_TENSE_MAPPINGS = [
|
|
23
|
+
'simple past', 'past perfect', 'past continuous', 'past perfect continuous',
|
|
24
|
+
'simple present', 'present perfect', 'present continuous', 'present perfect continuous',
|
|
25
|
+
'simple future', 'future perfect', 'future continuous', 'future perfect continuous',
|
|
26
|
+
];
|
|
27
|
+
// Languages statistics() scores readability for (plus 'auto').
|
|
28
|
+
const STATISTICS_LANGS = ['en', 'de', 'es', 'fr', 'it', 'nl', 'pl', 'ru', 'ar'];
|
|
11
29
|
|
|
12
30
|
// Shared by styleguide() and saveStyleguideRuleset(). Shape/type checks only —
|
|
13
31
|
// size limits (rule/description length) are server configuration and enforced
|
|
@@ -237,6 +255,286 @@ export class Client {
|
|
|
237
255
|
return this.axiosPromise(url, 'post', data);
|
|
238
256
|
}
|
|
239
257
|
|
|
258
|
+
/**
|
|
259
|
+
* Rephrase / paraphrase: generate alternative phrasings of a text.
|
|
260
|
+
*
|
|
261
|
+
* Returns `results`: one `{hash, original, replacement, rephrase_type,
|
|
262
|
+
* model_version}` entry per alternative. HTML is stripped server-side.
|
|
263
|
+
*
|
|
264
|
+
* @param {string} text - The text to rephrase (the API caps its length).
|
|
265
|
+
* @param {Object} [options]
|
|
266
|
+
* @param {string} [options.mapping] - Transformation to apply (see
|
|
267
|
+
* REPHRASE_MAPPINGS). Defaults to 'paraphrase'.
|
|
268
|
+
* @param {string} [options.tone_mapping] - Target tone; only used with
|
|
269
|
+
* mapping 'switch_tone'. Defaults to 'straightforward'.
|
|
270
|
+
* @param {string} [options.tense_mapping] - Target tense; only used with
|
|
271
|
+
* mapping 'switch_tense'. Defaults to 'simple past'.
|
|
272
|
+
* @param {number} [options.num_results] - Number of alternatives, 1–8
|
|
273
|
+
* (default 5). Honored by the paraphrase, informal_to_formal,
|
|
274
|
+
* formal_to_informal, generate_title and generate_acronym mappings; other
|
|
275
|
+
* mappings return their default number of results.
|
|
276
|
+
* @param {string} [options.session_id] - Optional session identifier used to
|
|
277
|
+
* group feedback events.
|
|
278
|
+
* @returns {Promise<AxiosResponse>}
|
|
279
|
+
*/
|
|
280
|
+
rephrase(text, options = {}) {
|
|
281
|
+
const url = `${this.urlEndpoint}rephrase`;
|
|
282
|
+
|
|
283
|
+
// Shape/type/enum checks only; the length cap is server configuration and
|
|
284
|
+
// enforced there with a clear 400 (see seo()). The enums and the 1–8
|
|
285
|
+
// num_results bound mirror the API's validators, which reject anything else.
|
|
286
|
+
if (typeof text !== 'string' || text.trim().length === 0) {
|
|
287
|
+
throw new TypeError('rephrase: text must be a non-empty string.');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
let data = {
|
|
291
|
+
text,
|
|
292
|
+
key: this.apiKey,
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// `!== undefined` (not `!= null`) so an explicit `null` is rejected as a bad
|
|
296
|
+
// type rather than silently treated as "omitted", matching classify()/seo().
|
|
297
|
+
if (options?.mapping !== undefined) {
|
|
298
|
+
if (!REPHRASE_MAPPINGS.includes(options.mapping)) {
|
|
299
|
+
throw new TypeError(`rephrase: mapping must be one of ${REPHRASE_MAPPINGS.join(', ')}.`);
|
|
300
|
+
}
|
|
301
|
+
data.mapping = options.mapping;
|
|
302
|
+
}
|
|
303
|
+
if (options?.tone_mapping !== undefined) {
|
|
304
|
+
if (!REPHRASE_TONE_MAPPINGS.includes(options.tone_mapping)) {
|
|
305
|
+
throw new TypeError(
|
|
306
|
+
`rephrase: tone_mapping must be one of ${REPHRASE_TONE_MAPPINGS.join(', ')}.`);
|
|
307
|
+
}
|
|
308
|
+
data.tone_mapping = options.tone_mapping;
|
|
309
|
+
}
|
|
310
|
+
if (options?.tense_mapping !== undefined) {
|
|
311
|
+
if (!REPHRASE_TENSE_MAPPINGS.includes(options.tense_mapping)) {
|
|
312
|
+
throw new TypeError(
|
|
313
|
+
`rephrase: tense_mapping must be one of ${REPHRASE_TENSE_MAPPINGS.join(', ')}.`);
|
|
314
|
+
}
|
|
315
|
+
data.tense_mapping = options.tense_mapping;
|
|
316
|
+
}
|
|
317
|
+
if (options?.num_results !== undefined) {
|
|
318
|
+
if (!Number.isInteger(options.num_results)
|
|
319
|
+
|| options.num_results < 1 || options.num_results > 8) {
|
|
320
|
+
throw new TypeError('rephrase: num_results must be an integer from 1 to 8.');
|
|
321
|
+
}
|
|
322
|
+
data.num_results = options.num_results;
|
|
323
|
+
}
|
|
324
|
+
if (options?.session_id !== undefined) {
|
|
325
|
+
if (typeof options.session_id !== 'string') {
|
|
326
|
+
throw new TypeError('rephrase: session_id must be a string.');
|
|
327
|
+
}
|
|
328
|
+
data.session_id = options.session_id;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return this.axiosPromise(url, 'post', data);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Sentiment analysis: positive / neutral / negative, overall and per sentence.
|
|
336
|
+
*
|
|
337
|
+
* Returns `sents` (the sentences found), `overall` (a list of
|
|
338
|
+
* `[probability, label]` pairs, most likely first) and `results` (one such
|
|
339
|
+
* list per sentence, aligned with `sents`). Labels are 'POSITIVE',
|
|
340
|
+
* 'NEUTRAL' or 'NEGATIVE'. A text with no sentences gets a 204 with no body
|
|
341
|
+
* (axios gives `data === ''`), and a text the API cannot split into
|
|
342
|
+
* sentences gets a 200 with `[]`.
|
|
343
|
+
*
|
|
344
|
+
* @param {string} text - The text to analyze, plain text or HTML (tags are
|
|
345
|
+
* stripped server-side; the API caps its length).
|
|
346
|
+
* @returns {Promise<AxiosResponse>}
|
|
347
|
+
*/
|
|
348
|
+
sentiment(text) {
|
|
349
|
+
const url = `${this.urlEndpoint}sentiment`;
|
|
350
|
+
|
|
351
|
+
if (typeof text !== 'string' || text.trim().length === 0) {
|
|
352
|
+
throw new TypeError('sentiment: text must be a non-empty string.');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
let data = {
|
|
356
|
+
text,
|
|
357
|
+
key: this.apiKey,
|
|
358
|
+
}
|
|
359
|
+
return this.axiosPromise(url, 'post', data);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Readability statistics: character / word / sentence counts, reading time
|
|
364
|
+
* and readability grades.
|
|
365
|
+
*
|
|
366
|
+
* Returns `chars`, `words`, `sentences`, `reading_time_sec`,
|
|
367
|
+
* `reading_time_min`, `readability` (a Flesch-Kincaid grade level, -3.4 when
|
|
368
|
+
* it cannot be scored) and `readability_scores` (a map of formula name to
|
|
369
|
+
* score; which formulas appear depends on `lang`, and it is empty for
|
|
370
|
+
* 'auto'). Unbilled.
|
|
371
|
+
*
|
|
372
|
+
* @param {string} text - The text to measure, plain text or HTML (tags are
|
|
373
|
+
* stripped server-side; the API caps it at 100,000 characters).
|
|
374
|
+
* @param {Object} [options]
|
|
375
|
+
* @param {string} [options.lang] - Language of the text: one of
|
|
376
|
+
* STATISTICS_LANGS ('en', 'de', 'es', 'fr', 'it', 'nl', 'pl', 'ru', 'ar')
|
|
377
|
+
* or 'auto'. Defaults to 'en'.
|
|
378
|
+
* @returns {Promise<AxiosResponse>}
|
|
379
|
+
*/
|
|
380
|
+
statistics(text, options = {}) {
|
|
381
|
+
const url = `${this.urlEndpoint}statistics`;
|
|
382
|
+
|
|
383
|
+
// An empty text is a valid request (all-zero counts), so only the type is
|
|
384
|
+
// checked here.
|
|
385
|
+
if (typeof text !== 'string') {
|
|
386
|
+
throw new TypeError('statistics: text must be a string.');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
let data = {
|
|
390
|
+
text,
|
|
391
|
+
key: this.apiKey,
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (options?.lang !== undefined) {
|
|
395
|
+
if (options.lang !== 'auto' && !STATISTICS_LANGS.includes(options.lang)) {
|
|
396
|
+
throw new TypeError(
|
|
397
|
+
`statistics: lang must be 'auto' or one of ${STATISTICS_LANGS.join(', ')}.`);
|
|
398
|
+
}
|
|
399
|
+
data.lang = options.lang;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return this.axiosPromise(url, 'post', data);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Similar terms: thesaurus synonyms plus semantically similar terms for a
|
|
407
|
+
* word or short phrase.
|
|
408
|
+
*
|
|
409
|
+
* Returns `neighbors` (up to 5 `[similarity, term]` pairs from embedding
|
|
410
|
+
* similarity, similarity 0–1, most similar first; only alphabetic terms)
|
|
411
|
+
* and `synonyms` (WordNet lemma names — multi-word ones use underscores,
|
|
412
|
+
* e.g. 'domestic_dog'). Unbilled.
|
|
413
|
+
*
|
|
414
|
+
* @param {string} query - The word or phrase to look up (the API uses only
|
|
415
|
+
* its first 100 characters).
|
|
416
|
+
* @returns {Promise<AxiosResponse>}
|
|
417
|
+
*/
|
|
418
|
+
similarTerms(query) {
|
|
419
|
+
const url = `${this.urlEndpoint}thesaurus`;
|
|
420
|
+
|
|
421
|
+
// The API answers a blank query with a 400.
|
|
422
|
+
if (typeof query !== 'string' || query.trim().length === 0) {
|
|
423
|
+
throw new TypeError('similarTerms: query must be a non-empty string.');
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
let data = {
|
|
427
|
+
query,
|
|
428
|
+
key: this.apiKey,
|
|
429
|
+
}
|
|
430
|
+
return this.axiosPromise(url, 'post', data);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Profanity filter: label each whitespace-separated token of a text.
|
|
435
|
+
*
|
|
436
|
+
* Returns `toks` (the text split on whitespace; punctuation stays attached,
|
|
437
|
+
* e.g. 'stupid.') and `labels` (aligned with `toks`: 1 for profanity, 0
|
|
438
|
+
* otherwise). The text is not HTML-stripped. Unbilled.
|
|
439
|
+
*
|
|
440
|
+
* @param {string} text - The text to check.
|
|
441
|
+
* @returns {Promise<AxiosResponse>}
|
|
442
|
+
*/
|
|
443
|
+
profanity(text) {
|
|
444
|
+
const url = `${this.urlEndpoint}profanity`;
|
|
445
|
+
|
|
446
|
+
if (typeof text !== 'string' || text.trim().length === 0) {
|
|
447
|
+
throw new TypeError('profanity: text must be a non-empty string.');
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
let data = {
|
|
451
|
+
text,
|
|
452
|
+
key: this.apiKey,
|
|
453
|
+
}
|
|
454
|
+
return this.axiosPromise(url, 'post', data);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* List the custom dictionary of this API key's account: words the grammar
|
|
459
|
+
* and spelling checks accept as correct.
|
|
460
|
+
*
|
|
461
|
+
* Returns a bare array of `{id, entry, case_sensitive, created_at,
|
|
462
|
+
* updated_at}` entries, sorted by entry (timestamps are RFC 1123 strings).
|
|
463
|
+
*
|
|
464
|
+
* @returns {Promise<AxiosResponse>}
|
|
465
|
+
*/
|
|
466
|
+
dictionaryEntries() {
|
|
467
|
+
const url = `${this.urlEndpoint}dictionary`;
|
|
468
|
+
// Not axiosPromise: this is a GET and the key rides the query string,
|
|
469
|
+
// like styleguideRulesets().
|
|
470
|
+
return axios({
|
|
471
|
+
url,
|
|
472
|
+
method: 'get',
|
|
473
|
+
params: { key: this.apiKey },
|
|
474
|
+
timeout: this.timeout,
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Add a word to this API key's account dictionary so the grammar and
|
|
480
|
+
* spelling checks stop flagging it (product names, jargon...).
|
|
481
|
+
*
|
|
482
|
+
* Returns the created `{id, entry, case_sensitive, created_at, updated_at}`
|
|
483
|
+
* entry. The API answers 400 for a duplicate entry ("Entry already
|
|
484
|
+
* exists.") and for an entry with more than two digits.
|
|
485
|
+
*
|
|
486
|
+
* @param {string} entry - The word to add (surrounding whitespace is
|
|
487
|
+
* stripped server-side).
|
|
488
|
+
* @param {Object} [options]
|
|
489
|
+
* @param {boolean} [options.case_sensitive] - Whether matching is case
|
|
490
|
+
* sensitive. Defaults to true.
|
|
491
|
+
* @returns {Promise<AxiosResponse>}
|
|
492
|
+
*/
|
|
493
|
+
addDictionaryEntry(entry, options = {}) {
|
|
494
|
+
const url = `${this.urlEndpoint}dictionary`;
|
|
495
|
+
|
|
496
|
+
if (typeof entry !== 'string' || entry.trim().length === 0) {
|
|
497
|
+
throw new TypeError('addDictionaryEntry: entry must be a non-empty string.');
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
let data = {
|
|
501
|
+
entry,
|
|
502
|
+
key: this.apiKey,
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (options?.case_sensitive !== undefined) {
|
|
506
|
+
if (typeof options.case_sensitive !== 'boolean') {
|
|
507
|
+
throw new TypeError('addDictionaryEntry: case_sensitive must be a boolean.');
|
|
508
|
+
}
|
|
509
|
+
data.case_sensitive = options.case_sensitive;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return this.axiosPromise(url, 'post', data);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Delete a custom dictionary entry by the `id` dictionaryEntries() or
|
|
517
|
+
* addDictionaryEntry() returned. Answers 204 with no body; an id that is
|
|
518
|
+
* malformed or not this account's gets a 400 (not a 404).
|
|
519
|
+
*
|
|
520
|
+
* @param {string} entryId - The entry's id (a UUID).
|
|
521
|
+
* @returns {Promise<AxiosResponse>}
|
|
522
|
+
*/
|
|
523
|
+
deleteDictionaryEntry(entryId) {
|
|
524
|
+
if (typeof entryId !== 'string' || entryId.trim().length === 0) {
|
|
525
|
+
throw new TypeError('deleteDictionaryEntry: entryId must be a non-empty string.');
|
|
526
|
+
}
|
|
527
|
+
const url = `${this.urlEndpoint}dictionary/${encodeURIComponent(entryId)}`;
|
|
528
|
+
// The key rides the query string, like deleteStyleguideRuleset(): some
|
|
529
|
+
// HTTP stacks drop DELETE bodies.
|
|
530
|
+
return axios({
|
|
531
|
+
url,
|
|
532
|
+
method: 'delete',
|
|
533
|
+
params: { key: this.apiKey },
|
|
534
|
+
timeout: this.timeout,
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
|
|
240
538
|
/**
|
|
241
539
|
* Structured data extraction: pull caller-named fields out of unstructured text.
|
|
242
540
|
*
|
|
@@ -866,6 +1164,18 @@ export class Client {
|
|
|
866
1164
|
if (options.sent_scores != null) {
|
|
867
1165
|
data.sent_scores = options.sent_scores;
|
|
868
1166
|
}
|
|
1167
|
+
if (options.score_string !== undefined) {
|
|
1168
|
+
if (typeof options.score_string !== 'boolean') {
|
|
1169
|
+
throw new TypeError('aidetect: score_string must be a boolean.');
|
|
1170
|
+
}
|
|
1171
|
+
data.score_string = options.score_string;
|
|
1172
|
+
}
|
|
1173
|
+
if (options.version !== undefined) {
|
|
1174
|
+
if (typeof options.version !== 'string') {
|
|
1175
|
+
throw new TypeError('aidetect: version must be a string.');
|
|
1176
|
+
}
|
|
1177
|
+
data.version = options.version;
|
|
1178
|
+
}
|
|
869
1179
|
|
|
870
1180
|
return this.axiosPromise(url, 'post', data);
|
|
871
1181
|
}
|