@vertana/core 0.1.0-dev.1

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.
Files changed (71) hide show
  1. package/LICENSE +20 -0
  2. package/dist/_virtual/rolldown_runtime.cjs +29 -0
  3. package/dist/accumulator.cjs +64 -0
  4. package/dist/accumulator.d.cts +51 -0
  5. package/dist/accumulator.d.ts +51 -0
  6. package/dist/accumulator.js +61 -0
  7. package/dist/chunking.cjs +76 -0
  8. package/dist/chunking.d.cts +124 -0
  9. package/dist/chunking.d.ts +124 -0
  10. package/dist/chunking.js +74 -0
  11. package/dist/context.cjs +51 -0
  12. package/dist/context.d.cts +148 -0
  13. package/dist/context.d.ts +148 -0
  14. package/dist/context.js +49 -0
  15. package/dist/evaluation.cjs +120 -0
  16. package/dist/evaluation.d.cts +111 -0
  17. package/dist/evaluation.d.ts +111 -0
  18. package/dist/evaluation.js +119 -0
  19. package/dist/glossary.cjs +0 -0
  20. package/dist/glossary.d.cts +25 -0
  21. package/dist/glossary.d.ts +25 -0
  22. package/dist/glossary.js +0 -0
  23. package/dist/html.cjs +253 -0
  24. package/dist/html.d.cts +41 -0
  25. package/dist/html.d.ts +41 -0
  26. package/dist/html.js +250 -0
  27. package/dist/index.cjs +39 -0
  28. package/dist/index.d.cts +17 -0
  29. package/dist/index.d.ts +17 -0
  30. package/dist/index.js +16 -0
  31. package/dist/markdown.cjs +300 -0
  32. package/dist/markdown.d.cts +17 -0
  33. package/dist/markdown.d.ts +17 -0
  34. package/dist/markdown.js +300 -0
  35. package/dist/plaintext.cjs +70 -0
  36. package/dist/plaintext.d.cts +17 -0
  37. package/dist/plaintext.d.ts +17 -0
  38. package/dist/plaintext.js +70 -0
  39. package/dist/prompt.cjs +91 -0
  40. package/dist/prompt.d.cts +74 -0
  41. package/dist/prompt.d.ts +74 -0
  42. package/dist/prompt.js +86 -0
  43. package/dist/refine.cjs +243 -0
  44. package/dist/refine.d.cts +148 -0
  45. package/dist/refine.d.ts +148 -0
  46. package/dist/refine.js +241 -0
  47. package/dist/select.cjs +62 -0
  48. package/dist/select.d.cts +83 -0
  49. package/dist/select.d.ts +83 -0
  50. package/dist/select.js +61 -0
  51. package/dist/terms.cjs +60 -0
  52. package/dist/terms.d.cts +36 -0
  53. package/dist/terms.d.ts +36 -0
  54. package/dist/terms.js +59 -0
  55. package/dist/tokens.cjs +40 -0
  56. package/dist/tokens.d.cts +24 -0
  57. package/dist/tokens.d.ts +24 -0
  58. package/dist/tokens.js +38 -0
  59. package/dist/tools.cjs +35 -0
  60. package/dist/tools.d.cts +20 -0
  61. package/dist/tools.d.ts +20 -0
  62. package/dist/tools.js +34 -0
  63. package/dist/translate.cjs +200 -0
  64. package/dist/translate.d.cts +190 -0
  65. package/dist/translate.d.ts +190 -0
  66. package/dist/translate.js +199 -0
  67. package/dist/window.cjs +0 -0
  68. package/dist/window.d.cts +48 -0
  69. package/dist/window.d.ts +48 -0
  70. package/dist/window.js +0 -0
  71. package/package.json +215 -0
@@ -0,0 +1,300 @@
1
+ import { countTokens } from "./tokens.js";
2
+
3
+ //#region src/markdown.ts
4
+ /**
5
+ * Checks if a line is an ATX-style heading.
6
+ *
7
+ * @param line The line to check.
8
+ * @returns The heading level (1-6) or 0 if not a heading.
9
+ */
10
+ function getAtxHeadingLevel(line) {
11
+ const match = line.match(/^(#{1,6})\s/);
12
+ if (match != null) return match[1].length;
13
+ return 0;
14
+ }
15
+ /**
16
+ * Checks if a line is a Setext-style heading underline.
17
+ *
18
+ * @param line The line to check.
19
+ * @returns The heading level (1 for =, 2 for -) or 0 if not an underline.
20
+ */
21
+ function getSetextUnderlineLevel(line) {
22
+ const trimmed = line.trim();
23
+ if (/^=+$/.test(trimmed) && trimmed.length >= 3) return 1;
24
+ if (/^-+$/.test(trimmed) && trimmed.length >= 3) return 2;
25
+ return 0;
26
+ }
27
+ /**
28
+ * Checks if a line starts a code fence at column 0 (not indented).
29
+ *
30
+ * @param line The line to check.
31
+ * @returns The fence pattern if it's a code fence start, null otherwise.
32
+ */
33
+ function getCodeFenceStart(line) {
34
+ const match = line.match(/^(`{3,}|~{3,})/);
35
+ if (match != null) return match[1];
36
+ return null;
37
+ }
38
+ /**
39
+ * Checks if a line closes a code fence.
40
+ *
41
+ * @param line The line to check.
42
+ * @param fenceChar The fence character (` or ~).
43
+ * @param fenceLength The minimum fence length.
44
+ * @returns True if the line closes the fence.
45
+ */
46
+ function isCodeFenceEnd(line, fenceChar, fenceLength) {
47
+ return (/* @__PURE__ */ new RegExp(`^${fenceChar}{${fenceLength},}\\s*$`)).test(line);
48
+ }
49
+ /**
50
+ * Parses Markdown content into sections.
51
+ *
52
+ * Each section starts with a heading (ATX or Setext style) and contains
53
+ * all content until the next heading of equal or higher level.
54
+ *
55
+ * @param text The Markdown text to parse.
56
+ * @returns An array of sections.
57
+ */
58
+ function parseIntoSections(text) {
59
+ const lines = text.split(/\r?\n/);
60
+ const sections = [];
61
+ let currentHeading = "";
62
+ let currentLevel = 0;
63
+ let currentLines = [];
64
+ let inCodeBlock = false;
65
+ let codeFenceChar = "";
66
+ let codeFenceLength = 0;
67
+ function flushSection() {
68
+ if (currentHeading.length > 0 || currentLines.length > 0) sections.push({
69
+ heading: currentHeading,
70
+ content: currentLines.join("\n").trim(),
71
+ level: currentLevel
72
+ });
73
+ currentHeading = "";
74
+ currentLevel = 0;
75
+ currentLines = [];
76
+ }
77
+ for (let i = 0; i < lines.length; i++) {
78
+ const line = lines[i];
79
+ if (inCodeBlock) {
80
+ currentLines.push(line);
81
+ if (isCodeFenceEnd(line, codeFenceChar, codeFenceLength)) {
82
+ inCodeBlock = false;
83
+ codeFenceChar = "";
84
+ codeFenceLength = 0;
85
+ }
86
+ continue;
87
+ }
88
+ const fence = getCodeFenceStart(line);
89
+ if (fence != null) {
90
+ currentLines.push(line);
91
+ inCodeBlock = true;
92
+ codeFenceChar = fence[0];
93
+ codeFenceLength = fence.length;
94
+ continue;
95
+ }
96
+ const atxLevel = getAtxHeadingLevel(line);
97
+ if (atxLevel > 0) {
98
+ flushSection();
99
+ currentHeading = line;
100
+ currentLevel = atxLevel;
101
+ continue;
102
+ }
103
+ if (i + 1 < lines.length && line.trim().length > 0 && !line.startsWith(" ")) {
104
+ const setextLevel = getSetextUnderlineLevel(lines[i + 1]);
105
+ if (setextLevel > 0) {
106
+ flushSection();
107
+ currentHeading = `${line}\n${lines[i + 1]}`;
108
+ currentLevel = setextLevel;
109
+ i++;
110
+ continue;
111
+ }
112
+ }
113
+ currentLines.push(line);
114
+ }
115
+ flushSection();
116
+ return sections;
117
+ }
118
+ /**
119
+ * Determines the primary content type of a section's content.
120
+ *
121
+ * @param content The section content.
122
+ * @returns The primary chunk type.
123
+ */
124
+ function getSectionContentType(content) {
125
+ const lines = content.split("\n").filter((l) => l.trim().length > 0);
126
+ if (lines.length === 0) return "paragraph";
127
+ let inCode = false;
128
+ let codeLines = 0;
129
+ let fenceChar = "";
130
+ let fenceLength = 0;
131
+ for (const line of lines) if (inCode) {
132
+ codeLines++;
133
+ if (isCodeFenceEnd(line, fenceChar, fenceLength)) inCode = false;
134
+ } else {
135
+ const fence = getCodeFenceStart(line);
136
+ if (fence != null) {
137
+ inCode = true;
138
+ fenceChar = fence[0];
139
+ fenceLength = fence.length;
140
+ codeLines++;
141
+ }
142
+ }
143
+ if (codeLines > lines.length / 2) return "code";
144
+ let listItemCount = 0;
145
+ let listContentLines = 0;
146
+ let inListItem = false;
147
+ let listMarkerIndent = -1;
148
+ for (const line of lines) {
149
+ const listMatch = line.match(/^(\s*)([-*+]|\d+\.)\s/);
150
+ if (listMatch != null) {
151
+ listItemCount++;
152
+ listContentLines++;
153
+ inListItem = true;
154
+ listMarkerIndent = listMatch[1].length;
155
+ } else if (inListItem) if ((line.match(/^(\s*)/)?.[1].length ?? 0) > listMarkerIndent) listContentLines++;
156
+ else inListItem = false;
157
+ }
158
+ if (listItemCount > 0 && listContentLines > lines.length / 2) return "list";
159
+ return "paragraph";
160
+ }
161
+ /**
162
+ * Splits text by sentences when line-level splitting isn't possible.
163
+ *
164
+ * @param text The text to split.
165
+ * @param maxTokens The maximum tokens per piece.
166
+ * @param countTokens The token counter function.
167
+ * @returns An array of text pieces.
168
+ */
169
+ function splitBySentences(text, maxTokens, countTokens$1) {
170
+ const sentences = text.split(/(?<=[.!?])\s+/);
171
+ const parts = [];
172
+ let currentPart = "";
173
+ for (const sentence of sentences) {
174
+ const newPart = currentPart.length > 0 ? `${currentPart} ${sentence}` : sentence;
175
+ if (countTokens$1(newPart) > maxTokens && currentPart.length > 0) {
176
+ parts.push(currentPart);
177
+ currentPart = sentence;
178
+ } else currentPart = newPart;
179
+ }
180
+ if (currentPart.length > 0) parts.push(currentPart);
181
+ return parts.length > 0 ? parts : [text];
182
+ }
183
+ /**
184
+ * Splits a section's content into smaller pieces if it exceeds the token limit.
185
+ *
186
+ * @param content The content to split.
187
+ * @param maxTokens The maximum tokens per piece.
188
+ * @param countTokens The token counter function.
189
+ * @returns An array of content pieces.
190
+ */
191
+ function splitContent(content, maxTokens, countTokens$1) {
192
+ if (countTokens$1(content) <= maxTokens) return [content];
193
+ const parts = [];
194
+ const paragraphs = content.split(/\n\n+/);
195
+ let currentPart = "";
196
+ for (const para of paragraphs) {
197
+ const newPart = currentPart.length > 0 ? `${currentPart}\n\n${para}` : para;
198
+ if (countTokens$1(newPart) > maxTokens) {
199
+ if (currentPart.length > 0) parts.push(currentPart);
200
+ if (countTokens$1(para) > maxTokens) {
201
+ const lines = para.split("\n");
202
+ let linePart = "";
203
+ for (const line of lines) {
204
+ const newLinePart = linePart.length > 0 ? `${linePart}\n${line}` : line;
205
+ if (countTokens$1(newLinePart) > maxTokens && linePart.length > 0) {
206
+ parts.push(linePart);
207
+ linePart = line;
208
+ } else linePart = newLinePart;
209
+ }
210
+ if (linePart.length > 0 && countTokens$1(linePart) > maxTokens) {
211
+ const sentenceParts = splitBySentences(linePart, maxTokens, countTokens$1);
212
+ for (let i = 0; i < sentenceParts.length - 1; i++) parts.push(sentenceParts[i]);
213
+ currentPart = sentenceParts[sentenceParts.length - 1];
214
+ } else if (linePart.length > 0) currentPart = linePart;
215
+ } else currentPart = para;
216
+ } else currentPart = newPart;
217
+ }
218
+ if (currentPart.length > 0) parts.push(currentPart);
219
+ return parts;
220
+ }
221
+ /**
222
+ * Creates a Markdown chunker.
223
+ *
224
+ * The chunker parses Markdown content into sections (heading + content) and
225
+ * creates chunks that respect section boundaries. Each section is kept as a
226
+ * single chunk when possible, and only split when exceeding the token limit.
227
+ *
228
+ * @returns A chunker function for Markdown content.
229
+ * @since 0.1.0
230
+ */
231
+ function createMarkdownChunker() {
232
+ return async (text, options) => {
233
+ const maxTokens = options?.maxTokens ?? 4096;
234
+ const countTokens$1 = options?.countTokens ?? countTokens;
235
+ const signal = options?.signal;
236
+ signal?.throwIfAborted();
237
+ await Promise.resolve();
238
+ const sections = parseIntoSections(text);
239
+ const chunks = [];
240
+ let chunkIndex = 0;
241
+ for (const section of sections) {
242
+ signal?.throwIfAborted();
243
+ const fullSection = section.heading.length > 0 && section.content.length > 0 ? `${section.heading}\n\n${section.content}` : section.heading.length > 0 ? section.heading : section.content;
244
+ if (fullSection.length === 0) continue;
245
+ if (countTokens$1(fullSection) <= maxTokens) {
246
+ chunks.push({
247
+ content: fullSection,
248
+ type: section.heading.length > 0 ? "section" : getSectionContentType(section.content),
249
+ index: chunkIndex++
250
+ });
251
+ continue;
252
+ }
253
+ if (section.heading.length > 0 && section.content.length > 0) {
254
+ const remainingTokens = maxTokens - countTokens$1(section.heading) - countTokens$1("\n\n");
255
+ if (remainingTokens > 0) {
256
+ const contentParts = splitContent(section.content, remainingTokens, countTokens$1);
257
+ if (contentParts.length > 0) {
258
+ chunks.push({
259
+ content: `${section.heading}\n\n${contentParts[0]}`,
260
+ type: "section",
261
+ index: chunkIndex++
262
+ });
263
+ for (let i = 1; i < contentParts.length; i++) chunks.push({
264
+ content: contentParts[i],
265
+ type: getSectionContentType(contentParts[i]),
266
+ index: chunkIndex++
267
+ });
268
+ } else chunks.push({
269
+ content: section.heading,
270
+ type: "heading",
271
+ index: chunkIndex++
272
+ });
273
+ } else {
274
+ chunks.push({
275
+ content: section.heading,
276
+ type: "heading",
277
+ index: chunkIndex++
278
+ });
279
+ const contentParts = splitContent(section.content, maxTokens, countTokens$1);
280
+ for (const part of contentParts) chunks.push({
281
+ content: part,
282
+ type: getSectionContentType(part),
283
+ index: chunkIndex++
284
+ });
285
+ }
286
+ } else {
287
+ const contentParts = splitContent(fullSection, maxTokens, countTokens$1);
288
+ for (const part of contentParts) chunks.push({
289
+ content: part,
290
+ type: getSectionContentType(part),
291
+ index: chunkIndex++
292
+ });
293
+ }
294
+ }
295
+ return chunks;
296
+ };
297
+ }
298
+
299
+ //#endregion
300
+ export { createMarkdownChunker };
@@ -0,0 +1,70 @@
1
+ const require_tokens = require('./tokens.cjs');
2
+
3
+ //#region src/plaintext.ts
4
+ /**
5
+ * Splits text by sentences when a paragraph exceeds the token limit.
6
+ *
7
+ * @param text The text to split.
8
+ * @param maxTokens The maximum tokens per piece.
9
+ * @param countTokens The token counter function.
10
+ * @returns An array of text pieces.
11
+ */
12
+ function splitBySentences(text, maxTokens, countTokens$1) {
13
+ const sentences = text.split(/(?<=[.!?])\s+/);
14
+ const parts = [];
15
+ let currentPart = "";
16
+ for (const sentence of sentences) {
17
+ const newPart = currentPart.length > 0 ? `${currentPart} ${sentence}` : sentence;
18
+ if (countTokens$1(newPart) > maxTokens && currentPart.length > 0) {
19
+ parts.push(currentPart);
20
+ currentPart = sentence;
21
+ } else currentPart = newPart;
22
+ }
23
+ if (currentPart.length > 0) parts.push(currentPart);
24
+ return parts.length > 0 ? parts : [text];
25
+ }
26
+ /**
27
+ * Creates a plain text chunker.
28
+ *
29
+ * The chunker splits plain text content by paragraphs (separated by one or
30
+ * more blank lines). When a paragraph exceeds the token limit, it is further
31
+ * split by sentences.
32
+ *
33
+ * @returns A chunker function for plain text content.
34
+ * @since 0.2.0
35
+ */
36
+ function createPlainTextChunker() {
37
+ return async (text, options) => {
38
+ const maxTokens = options?.maxTokens ?? 4096;
39
+ const countTokens$1 = options?.countTokens ?? require_tokens.countTokens;
40
+ const signal = options?.signal;
41
+ signal?.throwIfAborted();
42
+ await Promise.resolve();
43
+ const trimmed = text.trim();
44
+ if (trimmed.length === 0) return [];
45
+ const paragraphs = trimmed.split(/\n\s*\n+/).map((p) => p.trim()).filter((p) => p.length > 0);
46
+ const chunks = [];
47
+ let chunkIndex = 0;
48
+ for (const paragraph of paragraphs) {
49
+ signal?.throwIfAborted();
50
+ if (countTokens$1(paragraph) <= maxTokens) {
51
+ chunks.push({
52
+ content: paragraph,
53
+ type: "paragraph",
54
+ index: chunkIndex++
55
+ });
56
+ continue;
57
+ }
58
+ const parts = splitBySentences(paragraph, maxTokens, countTokens$1);
59
+ for (const part of parts) chunks.push({
60
+ content: part,
61
+ type: "paragraph",
62
+ index: chunkIndex++
63
+ });
64
+ }
65
+ return chunks;
66
+ };
67
+ }
68
+
69
+ //#endregion
70
+ exports.createPlainTextChunker = createPlainTextChunker;
@@ -0,0 +1,17 @@
1
+ import { Chunker } from "./chunking.cjs";
2
+
3
+ //#region src/plaintext.d.ts
4
+
5
+ /**
6
+ * Creates a plain text chunker.
7
+ *
8
+ * The chunker splits plain text content by paragraphs (separated by one or
9
+ * more blank lines). When a paragraph exceeds the token limit, it is further
10
+ * split by sentences.
11
+ *
12
+ * @returns A chunker function for plain text content.
13
+ * @since 0.2.0
14
+ */
15
+ declare function createPlainTextChunker(): Chunker;
16
+ //#endregion
17
+ export { createPlainTextChunker };
@@ -0,0 +1,17 @@
1
+ import { Chunker } from "./chunking.js";
2
+
3
+ //#region src/plaintext.d.ts
4
+
5
+ /**
6
+ * Creates a plain text chunker.
7
+ *
8
+ * The chunker splits plain text content by paragraphs (separated by one or
9
+ * more blank lines). When a paragraph exceeds the token limit, it is further
10
+ * split by sentences.
11
+ *
12
+ * @returns A chunker function for plain text content.
13
+ * @since 0.2.0
14
+ */
15
+ declare function createPlainTextChunker(): Chunker;
16
+ //#endregion
17
+ export { createPlainTextChunker };
@@ -0,0 +1,70 @@
1
+ import { countTokens } from "./tokens.js";
2
+
3
+ //#region src/plaintext.ts
4
+ /**
5
+ * Splits text by sentences when a paragraph exceeds the token limit.
6
+ *
7
+ * @param text The text to split.
8
+ * @param maxTokens The maximum tokens per piece.
9
+ * @param countTokens The token counter function.
10
+ * @returns An array of text pieces.
11
+ */
12
+ function splitBySentences(text, maxTokens, countTokens$1) {
13
+ const sentences = text.split(/(?<=[.!?])\s+/);
14
+ const parts = [];
15
+ let currentPart = "";
16
+ for (const sentence of sentences) {
17
+ const newPart = currentPart.length > 0 ? `${currentPart} ${sentence}` : sentence;
18
+ if (countTokens$1(newPart) > maxTokens && currentPart.length > 0) {
19
+ parts.push(currentPart);
20
+ currentPart = sentence;
21
+ } else currentPart = newPart;
22
+ }
23
+ if (currentPart.length > 0) parts.push(currentPart);
24
+ return parts.length > 0 ? parts : [text];
25
+ }
26
+ /**
27
+ * Creates a plain text chunker.
28
+ *
29
+ * The chunker splits plain text content by paragraphs (separated by one or
30
+ * more blank lines). When a paragraph exceeds the token limit, it is further
31
+ * split by sentences.
32
+ *
33
+ * @returns A chunker function for plain text content.
34
+ * @since 0.2.0
35
+ */
36
+ function createPlainTextChunker() {
37
+ return async (text, options) => {
38
+ const maxTokens = options?.maxTokens ?? 4096;
39
+ const countTokens$1 = options?.countTokens ?? countTokens;
40
+ const signal = options?.signal;
41
+ signal?.throwIfAborted();
42
+ await Promise.resolve();
43
+ const trimmed = text.trim();
44
+ if (trimmed.length === 0) return [];
45
+ const paragraphs = trimmed.split(/\n\s*\n+/).map((p) => p.trim()).filter((p) => p.length > 0);
46
+ const chunks = [];
47
+ let chunkIndex = 0;
48
+ for (const paragraph of paragraphs) {
49
+ signal?.throwIfAborted();
50
+ if (countTokens$1(paragraph) <= maxTokens) {
51
+ chunks.push({
52
+ content: paragraph,
53
+ type: "paragraph",
54
+ index: chunkIndex++
55
+ });
56
+ continue;
57
+ }
58
+ const parts = splitBySentences(paragraph, maxTokens, countTokens$1);
59
+ for (const part of parts) chunks.push({
60
+ content: part,
61
+ type: "paragraph",
62
+ index: chunkIndex++
63
+ });
64
+ }
65
+ return chunks;
66
+ };
67
+ }
68
+
69
+ //#endregion
70
+ export { createPlainTextChunker };
@@ -0,0 +1,91 @@
1
+
2
+ //#region src/prompt.ts
3
+ const languageNames = new Intl.DisplayNames(["en"], { type: "language" });
4
+ /**
5
+ * Gets the English display name for a language.
6
+ *
7
+ * @param language The language as an `Intl.Locale` or BCP 47 tag.
8
+ * @returns The English display name for the language.
9
+ */
10
+ function getLanguageName(language) {
11
+ const tag = typeof language === "string" ? language : language.toString();
12
+ return languageNames.of(tag) ?? tag;
13
+ }
14
+ /**
15
+ * Builds the system prompt for the translation.
16
+ *
17
+ * @param targetLanguage The target language for translation.
18
+ * @param options Additional options for the prompt.
19
+ * @returns The system prompt string.
20
+ */
21
+ function buildSystemPrompt(targetLanguage, options) {
22
+ const parts = [
23
+ "You are a professional translator.",
24
+ `Translate the given text into ${getLanguageName(targetLanguage)}.`,
25
+ "Preserve the original meaning, tone, and nuance as accurately as possible.",
26
+ "Output only the translated text without any explanations or notes."
27
+ ];
28
+ if (options?.sourceLanguage != null) {
29
+ const sourceLangName = getLanguageName(options.sourceLanguage);
30
+ parts.push(`The source language is ${sourceLangName}.`);
31
+ }
32
+ if (options?.tone != null) parts.push(`Use a ${options.tone} tone in the translation.`);
33
+ if (options?.domain != null) parts.push(`This text is from the ${options.domain} domain. Use appropriate terminology for this field.`);
34
+ if (options?.mediaType != null && options.mediaType !== "text/plain") {
35
+ const formatName = options.mediaType === "text/html" ? "HTML" : "Markdown";
36
+ parts.push(`The input is formatted as ${formatName}. Preserve the formatting structure in your translation.`);
37
+ }
38
+ if (options?.context != null) parts.push(`Additional context: ${options.context}`);
39
+ if (options?.glossary != null && options.glossary.length > 0) {
40
+ const glossaryLines = options.glossary.map((entry) => {
41
+ const contextNote = entry.context != null ? ` (${entry.context})` : "";
42
+ return ` - "${entry.original}" → "${entry.translated}"${contextNote}`;
43
+ });
44
+ parts.push("Use the following glossary for consistent terminology:\n" + glossaryLines.join("\n"));
45
+ }
46
+ return parts.join("\n\n");
47
+ }
48
+ /**
49
+ * Builds the user prompt for the translation.
50
+ *
51
+ * @param text The text to translate.
52
+ * @param title An optional title to include.
53
+ * @returns The user prompt string.
54
+ */
55
+ function buildUserPrompt(text, title) {
56
+ if (title != null) return `Title: ${title}\n\n${text}`;
57
+ return text;
58
+ }
59
+ /**
60
+ * Builds the user prompt with previous chunk context.
61
+ *
62
+ * @param text The text to translate.
63
+ * @param previousChunks Previously translated chunks for context.
64
+ * @returns The user prompt string with context.
65
+ */
66
+ function buildUserPromptWithContext(text, previousChunks) {
67
+ if (previousChunks.length === 0) return text;
68
+ return `The following sections have already been translated. Maintain consistency in terminology, style, and tone with the previous translations.
69
+
70
+ ${previousChunks.map((chunk, index) => {
71
+ return `[Previous section ${index + 1}]\nOriginal: ${chunk.source}\nTranslation: ${chunk.translation}`;
72
+ }).join("\n\n")}\n\n[Current section to translate]\n${text}`;
73
+ }
74
+ /**
75
+ * Extracts the translated title from the translated text.
76
+ *
77
+ * @param translatedText The translated text that may contain a title.
78
+ * @returns The extracted title, or undefined if not found.
79
+ */
80
+ function extractTitle(translatedText) {
81
+ const match = translatedText.match(/^Title:\s*(.+?)(?:\n|$)/);
82
+ if (match != null) return match[1].trim();
83
+ return translatedText.split("\n")[0]?.trim() || void 0;
84
+ }
85
+
86
+ //#endregion
87
+ exports.buildSystemPrompt = buildSystemPrompt;
88
+ exports.buildUserPrompt = buildUserPrompt;
89
+ exports.buildUserPromptWithContext = buildUserPromptWithContext;
90
+ exports.extractTitle = extractTitle;
91
+ exports.getLanguageName = getLanguageName;
@@ -0,0 +1,74 @@
1
+ import { Glossary } from "./glossary.cjs";
2
+
3
+ //#region src/prompt.d.ts
4
+
5
+ /**
6
+ * The media type of the input text.
7
+ *
8
+ * - `"text/plain"`: Plain text
9
+ * - `"text/html"`: HTML content
10
+ * - `"text/markdown"`: Markdown content
11
+ */
12
+ type MediaType = "text/plain" | "text/html" | "text/markdown";
13
+ /**
14
+ * The desired tone for the translated text.
15
+ */
16
+ type TranslationTone = "formal" | "informal" | "technical" | "casual" | "professional" | "literary" | "journalistic";
17
+ /**
18
+ * Gets the English display name for a language.
19
+ *
20
+ * @param language The language as an `Intl.Locale` or BCP 47 tag.
21
+ * @returns The English display name for the language.
22
+ */
23
+ declare function getLanguageName(language: Intl.Locale | string): string;
24
+ /**
25
+ * Options for building the system prompt.
26
+ */
27
+ interface SystemPromptOptions {
28
+ readonly sourceLanguage?: Intl.Locale | string;
29
+ readonly tone?: TranslationTone;
30
+ readonly domain?: string;
31
+ readonly mediaType?: MediaType;
32
+ readonly context?: string;
33
+ readonly glossary?: Glossary;
34
+ }
35
+ /**
36
+ * Builds the system prompt for the translation.
37
+ *
38
+ * @param targetLanguage The target language for translation.
39
+ * @param options Additional options for the prompt.
40
+ * @returns The system prompt string.
41
+ */
42
+ declare function buildSystemPrompt(targetLanguage: Intl.Locale | string, options?: SystemPromptOptions): string;
43
+ /**
44
+ * Represents a previously translated chunk for context.
45
+ */
46
+ interface TranslatedChunk {
47
+ readonly source: string;
48
+ readonly translation: string;
49
+ }
50
+ /**
51
+ * Builds the user prompt for the translation.
52
+ *
53
+ * @param text The text to translate.
54
+ * @param title An optional title to include.
55
+ * @returns The user prompt string.
56
+ */
57
+ declare function buildUserPrompt(text: string, title?: string): string;
58
+ /**
59
+ * Builds the user prompt with previous chunk context.
60
+ *
61
+ * @param text The text to translate.
62
+ * @param previousChunks Previously translated chunks for context.
63
+ * @returns The user prompt string with context.
64
+ */
65
+ declare function buildUserPromptWithContext(text: string, previousChunks: readonly TranslatedChunk[]): string;
66
+ /**
67
+ * Extracts the translated title from the translated text.
68
+ *
69
+ * @param translatedText The translated text that may contain a title.
70
+ * @returns The extracted title, or undefined if not found.
71
+ */
72
+ declare function extractTitle(translatedText: string): string | undefined;
73
+ //#endregion
74
+ export { MediaType, SystemPromptOptions, TranslatedChunk, TranslationTone, buildSystemPrompt, buildUserPrompt, buildUserPromptWithContext, extractTitle, getLanguageName };