speechready 0.0.1 → 0.1.0
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 +90 -3
- package/dist/changes.d.ts +14 -0
- package/dist/changes.js +10 -0
- package/dist/chunk.d.ts +32 -0
- package/dist/chunk.js +195 -0
- package/dist/clean.d.ts +41 -0
- package/dist/clean.js +323 -0
- package/dist/compile.d.ts +5 -0
- package/dist/compile.js +116 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +15 -0
- package/dist/intents.d.ts +34 -0
- package/dist/intents.js +92 -0
- package/dist/lexicon.d.ts +53 -0
- package/dist/lexicon.js +131 -0
- package/dist/pipeline.d.ts +34 -0
- package/dist/pipeline.js +49 -0
- package/dist/profiles/data.d.ts +1 -0
- package/dist/profiles/data.js +411 -0
- package/dist/profiles/index.d.ts +35 -0
- package/dist/profiles/index.js +72 -0
- package/dist/util.d.ts +53 -0
- package/dist/util.js +146 -0
- package/dist/verbalize/en.d.ts +3 -0
- package/dist/verbalize/en.js +219 -0
- package/dist/verbalize/index.d.ts +22 -0
- package/dist/verbalize/index.js +75 -0
- package/dist/verbalize/numbers_en.d.ts +8 -0
- package/dist/verbalize/numbers_en.js +83 -0
- package/dist/verbalize/numbers_tr.d.ts +24 -0
- package/dist/verbalize/numbers_tr.js +134 -0
- package/dist/verbalize/tr.d.ts +9 -0
- package/dist/verbalize/tr.js +204 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +2 -0
- package/package.json +24 -4
- package/index.js +0 -2
package/README.md
CHANGED
|
@@ -1,5 +1,92 @@
|
|
|
1
|
-
# speechready
|
|
1
|
+
# speechready (JavaScript / TypeScript)
|
|
2
2
|
|
|
3
|
-
Prepare text for text-to-speech
|
|
3
|
+
Prepare text for text-to-speech. See the [repository README](https://github.com/berkerbatur/speechready) for the project overview and the evaluation results.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
```
|
|
6
|
+
npm install speechready
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
ESM only, Node 20 or newer, no runtime dependencies. Type declarations are included. The provider profiles from `spec/profiles` are compiled into the package, so it also runs in a browser bundle.
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import { prepare } from "speechready";
|
|
15
|
+
|
|
16
|
+
const result = prepare(
|
|
17
|
+
"**Step 1:** run `npm install`. [pause 1s] Then read https://example.com/docs.",
|
|
18
|
+
{ provider: "elevenlabs", model: "eleven_multilingual_v2", locale: "en-US" },
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
result.text // 'Step 1: run npm install. <break time="1s" /> Then read example.com.'
|
|
22
|
+
result.chunks // the same text split under the model's request limit
|
|
23
|
+
for (const change of result.changes) {
|
|
24
|
+
console.log(change.rule, JSON.stringify(change.before), "->", JSON.stringify(change.after));
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`provider` and `model` pick a profile; with neither, the `plain` profile renders pauses as punctuation and returns one chunk. Pass `profile: loadProfile("azure-neural")` to pick one directly and `limit` to override the request limit. `locale` is `en-US` by default; `tr-TR` turns on the Turkish rules.
|
|
29
|
+
|
|
30
|
+
Everything a caller may want to decide differently is an option. The names are the same as in the Python package and in the shared fixtures, so they are snake_case; a name the package does not know throws.
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
prepare(text, { provider: "openai", options: {
|
|
34
|
+
code_blocks: "mention", // drop (default) | keep | mention: one sentence stands in for a fenced block
|
|
35
|
+
code_block_mention: null, // what `mention` says; null = "See the code block." / "Kod bloğuna bakın."
|
|
36
|
+
tables: "rows", // rows (default, each row a comma list) | drop
|
|
37
|
+
urls: "domain", // domain (default) | drop | keep
|
|
38
|
+
emails: "keep", // keep (default) | drop
|
|
39
|
+
emoji: "drop", // drop (default) | keep
|
|
40
|
+
emphasis: "strip", // strip (default) | intent: **bold** becomes an emphasis intent
|
|
41
|
+
list_numbers: "keep", // keep (default, "1. Open the app") | drop
|
|
42
|
+
fractions: "spoken", // spoken (default: "one half", "yarım") | division ("one over two", "bir bölü iki")
|
|
43
|
+
foreign_acronyms: true, // Turkish: npm, css, sql read with English letter names (en pi em)
|
|
44
|
+
terminal_punctuation: ".", // added to a block that ends without one
|
|
45
|
+
}});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Stages are also exported on their own: `clean(text, options, recorder, locale)`, `verbalize(text, locale, recorder, options)`, `splitSentences(text, locale)`, `pack(sentences, limit, unit)`, and `Segmenter` for streaming token feeds:
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
import { Segmenter } from "speechready";
|
|
52
|
+
|
|
53
|
+
const seg = new Segmenter({ locale: "en-US" });
|
|
54
|
+
for (const token of llmStream) {
|
|
55
|
+
for (const sentence of seg.push(token)) speak(sentence); // released once the boundary is confirmed
|
|
56
|
+
}
|
|
57
|
+
for (const sentence of seg.flush()) speak(sentence);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Pronunciation lexicon, one file for every vendor:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
import { Lexicon, prepare } from "speechready";
|
|
64
|
+
|
|
65
|
+
const lex = Lexicon.fromEntries([
|
|
66
|
+
{ word: "Nginx", ipa: "ˈɛndʒɪnˈɛks", say: "engine X" },
|
|
67
|
+
{ word: "SQL", say: "sequel" },
|
|
68
|
+
{ word: "CLI", spell: true },
|
|
69
|
+
]);
|
|
70
|
+
prepare("Restart Nginx.", { provider: "azure", lexicon: lex }).text;
|
|
71
|
+
// 'Restart <phoneme alphabet="ipa" ph="ˈɛndʒɪnˈɛks">Nginx</phoneme>.'
|
|
72
|
+
prepare("Restart Nginx.", { provider: "openai", lexicon: lex }).text;
|
|
73
|
+
// 'Restart engine X.' (no phoneme tags on OpenAI, so the respelling is used)
|
|
74
|
+
|
|
75
|
+
lex.toPls(); // PLS XML for ElevenLabs, Azure and Polly uploads
|
|
76
|
+
lex.toGoogleJson(); // Google Cloud TTS custom pronunciations
|
|
77
|
+
await Lexicon.load("lexicon.json"); // Node only; .yaml files need the optional `yaml` package
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Differences from the Python package
|
|
81
|
+
|
|
82
|
+
The two packages produce the same text for every fixture in `spec/fixtures`. The API differs only where the languages do: keyword arguments are one options object, methods are camelCase (`limitFor`, `toPls`, `splitSentences`), `Lexicon.load` is async, and `prepare` takes a `Lexicon` or an entries array rather than a file path.
|
|
83
|
+
|
|
84
|
+
## Development
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
cd js
|
|
88
|
+
npm ci
|
|
89
|
+
npm test # builds, then runs every fixture in ../spec/fixtures plus the package tests
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`npm run build` regenerates `src/profiles/data.ts` from `../spec/profiles` and `src/version.ts` from `package.json`, then compiles to `dist/`. All three are build outputs and are not committed.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** The audit trail: one Change per rewrite the pipeline made. */
|
|
2
|
+
export interface Change {
|
|
3
|
+
stage: string;
|
|
4
|
+
/** Dotted and stable, e.g. `clean.markdown.heading`. */
|
|
5
|
+
rule: string;
|
|
6
|
+
before: string;
|
|
7
|
+
after: string;
|
|
8
|
+
note: string;
|
|
9
|
+
}
|
|
10
|
+
/** Collects Changes; stages call `add` whenever they rewrite something. */
|
|
11
|
+
export declare class Recorder {
|
|
12
|
+
changes: Change[];
|
|
13
|
+
add(stage: string, rule: string, before: string, after: string, note?: string): void;
|
|
14
|
+
}
|
package/dist/changes.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** The audit trail: one Change per rewrite the pipeline made. */
|
|
2
|
+
/** Collects Changes; stages call `add` whenever they rewrite something. */
|
|
3
|
+
export class Recorder {
|
|
4
|
+
changes = [];
|
|
5
|
+
add(stage, rule, before, after, note = "") {
|
|
6
|
+
if (before === after)
|
|
7
|
+
return;
|
|
8
|
+
this.changes.push({ stage, rule, before, after, note });
|
|
9
|
+
}
|
|
10
|
+
}
|
package/dist/chunk.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Stage 4: chunk. Sentence segmentation, streaming segmentation, and packing under a limit.
|
|
2
|
+
*
|
|
3
|
+
* Rules that keep prosody intact:
|
|
4
|
+
* - a newline is a hard boundary (blocks from the clean stage never merge);
|
|
5
|
+
* - a sentence ends at . ! ? … followed by whitespace and then an uppercase
|
|
6
|
+
* letter, a digit, an opening quote or bracket, or a tag;
|
|
7
|
+
* - a known abbreviation, a single-letter initial, or a number before the full
|
|
8
|
+
* stop never ends a sentence;
|
|
9
|
+
* - a sentence that is longer than the limit is split at clause punctuation,
|
|
10
|
+
* then at spaces, and only as a last resort mid-word.
|
|
11
|
+
*/
|
|
12
|
+
/** Split text into sentences. Newlines are hard boundaries. Empty sentences are dropped. */
|
|
13
|
+
export declare function splitSentences(text: string, locale?: string): string[];
|
|
14
|
+
/** Streaming sentence segmenter for LLM token feeds.
|
|
15
|
+
*
|
|
16
|
+
* `push(text)` returns the sentences that are now complete; `flush()` returns
|
|
17
|
+
* whatever is left. A boundary is only confirmed once text after it arrives,
|
|
18
|
+
* so a sentence is never cut at "Dr." or "3.14" because the stream paused there.
|
|
19
|
+
*/
|
|
20
|
+
export declare class Segmenter {
|
|
21
|
+
locale: string;
|
|
22
|
+
maxBuffer: number | null;
|
|
23
|
+
private buffer;
|
|
24
|
+
constructor(init?: {
|
|
25
|
+
locale?: string;
|
|
26
|
+
maxBuffer?: number | null;
|
|
27
|
+
});
|
|
28
|
+
push(text: string): string[];
|
|
29
|
+
flush(): string[];
|
|
30
|
+
}
|
|
31
|
+
/** Greedily join sentences into chunks no larger than `limit`. null = one chunk. */
|
|
32
|
+
export declare function pack(sentences: string[], limit: number | null | undefined, unit?: string, joiner?: string): string[];
|
package/dist/chunk.js
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/** Stage 4: chunk. Sentence segmentation, streaming segmentation, and packing under a limit.
|
|
2
|
+
*
|
|
3
|
+
* Rules that keep prosody intact:
|
|
4
|
+
* - a newline is a hard boundary (blocks from the clean stage never merge);
|
|
5
|
+
* - a sentence ends at . ! ? … followed by whitespace and then an uppercase
|
|
6
|
+
* letter, a digit, an opening quote or bracket, or a tag;
|
|
7
|
+
* - a known abbreviation, a single-letter initial, or a number before the full
|
|
8
|
+
* stop never ends a sentence;
|
|
9
|
+
* - a sentence that is longer than the limit is split at clause punctuation,
|
|
10
|
+
* then at spaces, and only as a last resort mid-word.
|
|
11
|
+
*/
|
|
12
|
+
import { SENTINEL_OPEN } from "./intents.js";
|
|
13
|
+
import { codePoints, finditer, fullmatch, isAlpha, isDigit, isUpper, re, rstrip, strip, utf8Length } from "./util.js";
|
|
14
|
+
const ABBREVIATIONS = {
|
|
15
|
+
en: new Set(`mr mrs ms dr prof sr jr st mt ft vs etc e.g i.e cf no nos inc ltd co corp llc dept est approx
|
|
16
|
+
jan feb mar apr jun jul aug sep sept oct nov dec mon tue wed thu fri sat sun a.m p.m
|
|
17
|
+
u.s u.k e.u ph.d m.d b.a m.a b.sc m.sc fig vol pp ed eds rev gen col lt sgt capt cmdr hon
|
|
18
|
+
ave blvd rd hwy tel ext dept univ assn bros`.split(/\s+/)),
|
|
19
|
+
tr: new Set(`dr prof doç yrd av sn vb vs vd örn bkz yy t.c no sok cad apt mah alb yzb bşk müh
|
|
20
|
+
oca şub mar nis haz tem ağu eyl eki kas ara ltd şti a.ş san tic dk sa sn bk ör
|
|
21
|
+
hz sf s c çev der haz üniv fak böl hst bul uzm öğr yrd tel`.split(/\s+/)),
|
|
22
|
+
};
|
|
23
|
+
const END = re(String.raw `(?<end>[.!?…]+)(?<close>["'”’)\]»]*)(?<space>\s+)(?=(?<next>\S))`);
|
|
24
|
+
const WORD_BEFORE = re(String.raw `(\S+)$`);
|
|
25
|
+
const CLAUSE = re(String.raw `[,;:—–]\s+|\s+[-]\s+`);
|
|
26
|
+
const DOTTED = re("[a-z](\\.[a-z])+");
|
|
27
|
+
const LIST_MARKER = re(String.raw `\d{1,3}\.`);
|
|
28
|
+
function lang(locale) {
|
|
29
|
+
return (locale || "en").split("-")[0].split("_")[0].toLowerCase();
|
|
30
|
+
}
|
|
31
|
+
function abbreviations(locale) {
|
|
32
|
+
return ABBREVIATIONS[lang(locale)] ?? new Set();
|
|
33
|
+
}
|
|
34
|
+
function isBoundary(text, m, abbrs) {
|
|
35
|
+
const g = m.groups ?? {};
|
|
36
|
+
const end = g.end ?? "";
|
|
37
|
+
const nxt = g.next ?? "";
|
|
38
|
+
const before = text.slice(0, m.index);
|
|
39
|
+
const wordM = WORD_BEFORE.exec(before);
|
|
40
|
+
WORD_BEFORE.lastIndex = 0;
|
|
41
|
+
const word = wordM ? wordM[1] : "";
|
|
42
|
+
const core = strip(word, "(\"'“”‘’[").toLowerCase();
|
|
43
|
+
if ((g.space ?? "").includes("\n"))
|
|
44
|
+
return true;
|
|
45
|
+
if (end.startsWith(".") && !end.includes("…") && !end.includes("!") && !end.includes("?")) {
|
|
46
|
+
if (abbrs.has(core) || abbrs.has(rstrip(core, ".")))
|
|
47
|
+
return false;
|
|
48
|
+
if (core.length === 1 && isAlpha(core))
|
|
49
|
+
return false; // initial: "J. R. R. Tolkien"
|
|
50
|
+
if (isDigit(core) && !isUpper(nxt[0]))
|
|
51
|
+
return false; // ordinal or list number: "3. madde"
|
|
52
|
+
if (fullmatch(DOTTED, core))
|
|
53
|
+
return false; // dotted abbreviation not in the list, e.g. "p.s"
|
|
54
|
+
}
|
|
55
|
+
const first = nxt[0];
|
|
56
|
+
if ("\"'“‘([«<".includes(first) || first === SENTINEL_OPEN)
|
|
57
|
+
return true;
|
|
58
|
+
if (isUpper(first) || isDigit(first))
|
|
59
|
+
return true;
|
|
60
|
+
if ("¿¡".includes(first))
|
|
61
|
+
return true;
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
/** Split text into sentences. Newlines are hard boundaries. Empty sentences are dropped. */
|
|
65
|
+
export function splitSentences(text, locale = "en-US") {
|
|
66
|
+
const abbrs = abbreviations(locale);
|
|
67
|
+
const out = [];
|
|
68
|
+
for (const rawLine of text.split("\n")) {
|
|
69
|
+
const line = rawLine.trim();
|
|
70
|
+
if (!line)
|
|
71
|
+
continue;
|
|
72
|
+
let start = 0;
|
|
73
|
+
for (const m of finditer(END, line)) {
|
|
74
|
+
const g = m.groups ?? {};
|
|
75
|
+
const endAll = m.index + m[0].length;
|
|
76
|
+
const endClose = m.index + (g.end ?? "").length + (g.close ?? "").length;
|
|
77
|
+
if (fullmatch(LIST_MARKER, line.slice(start, endAll).trim()))
|
|
78
|
+
continue; // "1. Open the app": the number is the item's marker, not a sentence
|
|
79
|
+
if (isBoundary(line, m, abbrs)) {
|
|
80
|
+
out.push(line.slice(start, endClose).trim());
|
|
81
|
+
start = endAll;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const tail = line.slice(start).trim();
|
|
85
|
+
if (tail)
|
|
86
|
+
out.push(tail);
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
/** Streaming sentence segmenter for LLM token feeds.
|
|
91
|
+
*
|
|
92
|
+
* `push(text)` returns the sentences that are now complete; `flush()` returns
|
|
93
|
+
* whatever is left. A boundary is only confirmed once text after it arrives,
|
|
94
|
+
* so a sentence is never cut at "Dr." or "3.14" because the stream paused there.
|
|
95
|
+
*/
|
|
96
|
+
export class Segmenter {
|
|
97
|
+
locale;
|
|
98
|
+
maxBuffer;
|
|
99
|
+
buffer = "";
|
|
100
|
+
constructor(init = {}) {
|
|
101
|
+
this.locale = init.locale ?? "en-US";
|
|
102
|
+
this.maxBuffer = init.maxBuffer ?? null;
|
|
103
|
+
}
|
|
104
|
+
push(text) {
|
|
105
|
+
this.buffer += text;
|
|
106
|
+
const done = [];
|
|
107
|
+
for (;;) {
|
|
108
|
+
const nl = this.buffer.indexOf("\n");
|
|
109
|
+
if (nl < 0)
|
|
110
|
+
break;
|
|
111
|
+
const head = this.buffer.slice(0, nl);
|
|
112
|
+
this.buffer = this.buffer.slice(nl + 1);
|
|
113
|
+
done.push(...splitSentences(head, this.locale));
|
|
114
|
+
}
|
|
115
|
+
const sentences = splitSentences(this.buffer, this.locale);
|
|
116
|
+
if (sentences.length > 1) {
|
|
117
|
+
done.push(...sentences.slice(0, -1));
|
|
118
|
+
// keep the unconfirmed tail exactly as it stands (leading spaces dropped)
|
|
119
|
+
const idx = this.buffer.lastIndexOf(sentences[sentences.length - 1]);
|
|
120
|
+
this.buffer = idx >= 0 ? this.buffer.slice(idx) : this.buffer;
|
|
121
|
+
}
|
|
122
|
+
if (this.maxBuffer && this.buffer.length > this.maxBuffer) {
|
|
123
|
+
const cut = lastClauseCut(this.buffer, this.maxBuffer);
|
|
124
|
+
if (cut > 0) {
|
|
125
|
+
done.push(this.buffer.slice(0, cut).trim());
|
|
126
|
+
this.buffer = this.buffer.slice(cut).replace(/^\s+/, "");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return done.filter((s) => s);
|
|
130
|
+
}
|
|
131
|
+
flush() {
|
|
132
|
+
const rest = this.buffer;
|
|
133
|
+
this.buffer = "";
|
|
134
|
+
return splitSentences(rest, this.locale);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function lastClauseCut(text, limit) {
|
|
138
|
+
const window = text.slice(0, limit);
|
|
139
|
+
let best = -1;
|
|
140
|
+
for (const m of finditer(CLAUSE, window))
|
|
141
|
+
best = m.index + m[0].length;
|
|
142
|
+
if (best > 0)
|
|
143
|
+
return best;
|
|
144
|
+
const sp = window.lastIndexOf(" ");
|
|
145
|
+
return sp > 0 ? sp + 1 : limit;
|
|
146
|
+
}
|
|
147
|
+
function size(text, unit) {
|
|
148
|
+
return unit === "bytes" ? utf8Length(text) : codePoints(text);
|
|
149
|
+
}
|
|
150
|
+
/** Split one over-long sentence at clause marks, then spaces, then hard. */
|
|
151
|
+
function splitLong(sentence, limit, unit) {
|
|
152
|
+
if (size(sentence, unit) <= limit)
|
|
153
|
+
return [sentence];
|
|
154
|
+
const pieces = [];
|
|
155
|
+
let rest = sentence;
|
|
156
|
+
while (size(rest, unit) > limit) {
|
|
157
|
+
let window = rest;
|
|
158
|
+
while (size(window, unit) > limit)
|
|
159
|
+
window = window.slice(0, Math.max(1, Math.floor(window.length * 0.9)));
|
|
160
|
+
let cut = -1;
|
|
161
|
+
for (const m of finditer(CLAUSE, window))
|
|
162
|
+
cut = m.index + m[0].length;
|
|
163
|
+
if (cut <= 0)
|
|
164
|
+
cut = window.lastIndexOf(" ") + 1;
|
|
165
|
+
if (cut <= 0)
|
|
166
|
+
cut = window.length;
|
|
167
|
+
pieces.push(rest.slice(0, cut).trim());
|
|
168
|
+
rest = rest.slice(cut).trim();
|
|
169
|
+
}
|
|
170
|
+
if (rest)
|
|
171
|
+
pieces.push(rest);
|
|
172
|
+
return pieces;
|
|
173
|
+
}
|
|
174
|
+
/** Greedily join sentences into chunks no larger than `limit`. null = one chunk. */
|
|
175
|
+
export function pack(sentences, limit, unit = "chars", joiner = " ") {
|
|
176
|
+
if (limit === null || limit === undefined)
|
|
177
|
+
return sentences.length ? [sentences.join(joiner)] : [];
|
|
178
|
+
const chunks = [];
|
|
179
|
+
let current = "";
|
|
180
|
+
for (const s of sentences) {
|
|
181
|
+
for (const piece of splitLong(s, limit, unit)) {
|
|
182
|
+
const candidate = current ? current + joiner + piece : piece;
|
|
183
|
+
if (current && size(candidate, unit) > limit) {
|
|
184
|
+
chunks.push(current);
|
|
185
|
+
current = piece;
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
current = candidate;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (current)
|
|
193
|
+
chunks.push(current);
|
|
194
|
+
return chunks;
|
|
195
|
+
}
|
package/dist/clean.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Stage 1: clean. Turn markdown, code, emoji, URLs and invisible Unicode into plain prose.
|
|
2
|
+
*
|
|
3
|
+
* Block structure is preserved as one block per line: headings, paragraphs,
|
|
4
|
+
* list items and table rows each become a line, and a block that ends without
|
|
5
|
+
* terminal punctuation gets a full stop so the model pauses after it. Hard-wrapped
|
|
6
|
+
* lines inside a paragraph are joined with a space.
|
|
7
|
+
*/
|
|
8
|
+
import { Recorder } from "./changes.js";
|
|
9
|
+
/** Everything a caller can change about how text is prepared. Defaults are what the evaluation ran with.
|
|
10
|
+
*
|
|
11
|
+
* Field names are the same as in the Python package and the shared fixtures (snake_case).
|
|
12
|
+
*/
|
|
13
|
+
export interface Options {
|
|
14
|
+
/** drop | keep | mention (one spoken sentence stands in for the block) */
|
|
15
|
+
code_blocks: "drop" | "keep" | "mention";
|
|
16
|
+
/** the sentence `mention` speaks; null = the locale's default */
|
|
17
|
+
code_block_mention: string | null;
|
|
18
|
+
tables: "rows" | "drop";
|
|
19
|
+
urls: "domain" | "drop" | "keep";
|
|
20
|
+
emails: "keep" | "drop";
|
|
21
|
+
emoji: "drop" | "keep";
|
|
22
|
+
emphasis: "strip" | "intent";
|
|
23
|
+
/** keep ("1. Open the app") | drop ("Open the app") */
|
|
24
|
+
list_numbers: "keep" | "drop";
|
|
25
|
+
strip_tags: string[];
|
|
26
|
+
terminal_punctuation: string;
|
|
27
|
+
html: "strip" | "keep";
|
|
28
|
+
/** spoken ("one half", "yarım") | division ("one over two", "bir bölü iki") */
|
|
29
|
+
fractions: "spoken" | "division";
|
|
30
|
+
/** Turkish: lowercase vowel-less tokens like npm, css read with English letter names */
|
|
31
|
+
foreign_acronyms: boolean;
|
|
32
|
+
extra: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
export declare const DEFAULT_OPTIONS: Options;
|
|
35
|
+
/** Fill in defaults. Unknown keys throw, so a camelCase typo is not silently ignored. */
|
|
36
|
+
export declare function options(partial?: Partial<Options> | null): Options;
|
|
37
|
+
/** Run the clean stage on `text`; returns the cleaned text and records changes.
|
|
38
|
+
*
|
|
39
|
+
* `locale` only picks the sentence that `{code_blocks: "mention"}` speaks.
|
|
40
|
+
*/
|
|
41
|
+
export declare function clean(text: string, opts?: Partial<Options> | null, recorder?: Recorder, locale?: string): string;
|