@gringow/gringow 0.0.1 → 0.0.4

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 (42) hide show
  1. package/.nvmrc +1 -0
  2. package/README.md +86 -2
  3. package/gringow.config.js.example +16 -0
  4. package/gringow.config.json.example +11 -0
  5. package/gringow.json +12 -0
  6. package/package.json +68 -4
  7. package/src/browser.ts +4 -0
  8. package/src/config.ts +93 -0
  9. package/src/data/check-cache.ts +15 -0
  10. package/src/data/clear-cache.ts +5 -0
  11. package/src/data/create-cache-dir.ts +7 -0
  12. package/src/data/create-cache-id.ts +17 -0
  13. package/src/data/delete-cache-item.ts +20 -0
  14. package/src/data/get-cache.ts +17 -0
  15. package/src/data/get-or-create-cache.ts +21 -0
  16. package/src/data/save-cache.ts +17 -0
  17. package/src/data/write-cache-to-disk.ts +20 -0
  18. package/src/default-config.ts +27 -0
  19. package/src/flat-template-string.test.ts +109 -0
  20. package/src/flat-template-string.ts +27 -0
  21. package/src/gringow.ts +49 -0
  22. package/src/index.ts +37 -0
  23. package/src/io/anthropic/ask.ts +31 -0
  24. package/src/io/anthropic/commit-response.ts +11 -0
  25. package/src/io/anthropic/index.ts +3 -0
  26. package/src/io/anthropic/parse.ts +11 -0
  27. package/src/io/ask-llm.ts +44 -0
  28. package/src/io/gemini/ask.ts +65 -0
  29. package/src/io/gemini/commit-response.ts +6 -0
  30. package/src/io/gemini/index.ts +3 -0
  31. package/src/io/gemini/parse.ts +12 -0
  32. package/src/io/huggingface/ask.ts +82 -0
  33. package/src/io/huggingface/commit-response.ts +16 -0
  34. package/src/io/huggingface/index.ts +16 -0
  35. package/src/io/huggingface/languagesMap.ts +189 -0
  36. package/src/io/huggingface/parse.ts +10 -0
  37. package/src/logger.ts +25 -0
  38. package/src/schemas/config.shcema.ts +58 -0
  39. package/src/types/index.ts +22 -0
  40. package/tsconfig.json +29 -0
  41. package/tsup.config.ts +21 -0
  42. package/vitest.config.ts +8 -0
package/src/index.ts CHANGED
@@ -0,0 +1,37 @@
1
+ import '@/config'
2
+
3
+ export type GringowTranslation = {
4
+ [key: string]: string
5
+ }
6
+
7
+ export type GringowCacheItem = {
8
+ original: string
9
+ translation: GringowTranslation
10
+ }
11
+
12
+ export type GringowCache = {
13
+ [key: string]: GringowCacheItem
14
+ }
15
+
16
+ export type GringowResponse = {
17
+ cacheId: string
18
+ translation: GringowCacheItem
19
+ }
20
+
21
+ export type GringowError = Error & {
22
+ code?: string
23
+ cause?: Error
24
+ }
25
+
26
+ export { awaitedConfig, getConfigSource, isLocalConfigFound } from '@/config'
27
+ export { checkCache } from '@/data/check-cache'
28
+ export { clearCache } from '@/data/clear-cache'
29
+ export { createCacheId } from '@/data/create-cache-id'
30
+ export { deleteCacheItem } from '@/data/delete-cache-item'
31
+ export { getCache } from '@/data/get-cache'
32
+ export { getOrCreateCache } from '@/data/get-or-create-cache'
33
+ export { writeCacheToDisk } from '@/data/write-cache-to-disk'
34
+ export { flatTemplateString } from '@/flat-template-string'
35
+ export { Gringow as default } from '@/gringow'
36
+
37
+ export type { GringowConfig } from '@/default-config'
@@ -0,0 +1,31 @@
1
+ import Anthropic from '@anthropic-ai/sdk'
2
+ import { awaitedConfig } from '@/config'
3
+
4
+ export async function askAnthropic(message: string) {
5
+ const config = await awaitedConfig
6
+ if (Number(config.llm.sleep)) {
7
+ await new Promise((resolve) => setTimeout(resolve, config.llm.sleep ?? 1))
8
+ }
9
+
10
+ if (!config.keys.anthropicApiKey) {
11
+ throw new Error('Anthropic API key is not set in the config')
12
+ }
13
+
14
+ const anthropic = new Anthropic({
15
+ apiKey: config.keys.anthropicApiKey,
16
+ dangerouslyAllowBrowser: true,
17
+ })
18
+
19
+ const response = await anthropic.messages.create({
20
+ model: config.llm.model as Anthropic.Model,
21
+ max_tokens: 1024,
22
+ messages: [
23
+ {
24
+ role: 'user',
25
+ content: `${config.llm.prePrompt}\n\n\n${message}\n\n\n${config.llm.postPrompt}`,
26
+ },
27
+ ],
28
+ })
29
+
30
+ return response as Anthropic.Messages.Message
31
+ }
@@ -0,0 +1,11 @@
1
+ import type { Anthropic } from '@anthropic-ai/sdk'
2
+ import type { CommitResponse } from '@/types'
3
+
4
+ export function commitResponseAnthropic(response: Anthropic.Messages.Message) {
5
+ const result = response.content.reduce((acc: string, item: Anthropic.Messages.ContentBlock) => {
6
+ const nextAcc = acc + (item.type === 'text' ? item.text : '')
7
+ return nextAcc
8
+ }, '')
9
+
10
+ return result as CommitResponse
11
+ }
@@ -0,0 +1,3 @@
1
+ export * from './ask'
2
+ export * from './commit-response'
3
+ export * from './parse'
@@ -0,0 +1,11 @@
1
+ import type { GringowCacheItem } from '@/index'
2
+ import type { CommitResponse } from '@/types'
3
+
4
+ export function parseAnthropic(unparsed: CommitResponse): GringowCacheItem {
5
+ if (typeof unparsed !== 'string') {
6
+ throw new Error('CommitResponse should be a string here.')
7
+ }
8
+
9
+ const parsed = JSON.parse(unparsed) as GringowCacheItem
10
+ return parsed
11
+ }
@@ -0,0 +1,44 @@
1
+ import { awaitedConfig } from '@/config'
2
+ import { askAnthropic, commitResponseAnthropic, parseAnthropic } from './anthropic'
3
+ import { askGemini, commitResponseGemini, parseGemini } from './gemini'
4
+ import { askHuggingface, commitResponseHuggingface, parseHuggingface } from './huggingface'
5
+
6
+ import type { GringowCacheItem } from '@/index'
7
+ import type { LLMChoice, LLMChoices, LLMOptions } from '@/types'
8
+
9
+ const llmChoices: LLMChoices = {
10
+ anthropic: {
11
+ ask: askAnthropic,
12
+ commitResponse: commitResponseAnthropic,
13
+ parse: parseAnthropic,
14
+ },
15
+ gemini: {
16
+ ask: askGemini,
17
+ commitResponse: commitResponseGemini,
18
+ parse: parseGemini,
19
+ },
20
+ huggingface: {
21
+ ask: askHuggingface,
22
+ commitResponse: commitResponseHuggingface,
23
+ parse: parseHuggingface,
24
+ },
25
+ }
26
+
27
+ async function processChoice<T = { test: number }>(choice: LLMChoice<T>, prompt: string): Promise<GringowCacheItem> {
28
+ const response = await choice.ask(prompt)
29
+ const unparsed = choice.commitResponse(response)
30
+ const parsed = choice.parse(unparsed)
31
+ return parsed
32
+ }
33
+
34
+ export async function askLLM(prompt: string): Promise<GringowCacheItem> {
35
+ const config = await awaitedConfig
36
+ const llmChoiceName = config.llm.choice as LLMOptions
37
+ const llmChoice = llmChoices[llmChoiceName]
38
+
39
+ if (!llmChoice) {
40
+ throw new Error('LLMChoice not found')
41
+ }
42
+
43
+ return processChoice<Awaited<ReturnType<typeof llmChoice.ask>>>(llmChoice, prompt)
44
+ }
@@ -0,0 +1,65 @@
1
+ import { type GenerateContentResult, GoogleGenerativeAI } from '@google/generative-ai'
2
+
3
+ import { awaitedConfig } from '@/config'
4
+
5
+ async function generateResponse(message: string, temperature: number = 1.0) {
6
+ const config = await awaitedConfig
7
+ if (!config.keys.geminiApiKey) {
8
+ throw new Error('Gemini API key is not set in the config')
9
+ }
10
+
11
+ const generativeAIClient = new GoogleGenerativeAI(config.keys.geminiApiKey)
12
+ const model = generativeAIClient.getGenerativeModel({
13
+ model: config.llm.model as string,
14
+ generationConfig: {
15
+ temperature,
16
+ },
17
+ })
18
+
19
+ const prompt = String(`${config.llm.prePrompt}\n\n\n${message}\n\n\n${config.llm.postPrompt}`).trim()
20
+ if (config.debug) {
21
+ console.log('Gemini model prompt:', JSON.stringify({ prompt }))
22
+ }
23
+
24
+ const response = await model.generateContent([prompt])
25
+
26
+ return response
27
+ }
28
+
29
+ export async function askGemini(message: string) {
30
+ const config = await awaitedConfig
31
+ if (Number(config.llm.sleep)) {
32
+ await new Promise((resolve) => setTimeout(resolve, config.llm.sleep ?? 1))
33
+ }
34
+
35
+ if (!config.keys.geminiApiKey) {
36
+ throw new Error('Gemini API key is not set in the config')
37
+ }
38
+
39
+ if (config.debug) {
40
+ console.log('\nAsking Gemini model:', JSON.stringify({ model: config.llm.model, message }))
41
+ }
42
+
43
+ let response: GenerateContentResult | undefined
44
+ for (let i = 0; i < 3; i++) {
45
+ const maybeResponse = await generateResponse(message, config.llm.temperature)
46
+ if (!String(maybeResponse.response.candidates?.[0]?.content?.parts?.[0]?.text ?? '').includes('```json')) {
47
+ response = maybeResponse
48
+ break
49
+ }
50
+
51
+ if (config.debug) {
52
+ console.log('Gemini model response is not valid, contains no JSON:', JSON.stringify({ maybeResponse }))
53
+ }
54
+ }
55
+
56
+ if (config.debug) {
57
+ console.log('Gemini model response:', JSON.stringify({ response }))
58
+ }
59
+
60
+ if (!response) {
61
+ throw new Error('Gemini model response is not valid')
62
+ }
63
+
64
+ return response
65
+ }
@@ -0,0 +1,6 @@
1
+ import type { GenerateContentResult } from '@google/generative-ai'
2
+
3
+ export function commitResponseGemini(response: GenerateContentResult): string {
4
+ const result = response.response.text()
5
+ return result
6
+ }
@@ -0,0 +1,3 @@
1
+ export * from './ask'
2
+ export * from './commit-response'
3
+ export * from './parse'
@@ -0,0 +1,12 @@
1
+ import type { GringowCacheItem } from '@/index'
2
+ import type { CommitResponse } from '@/types'
3
+
4
+ export function parseGemini(unparsed: CommitResponse): GringowCacheItem {
5
+ if (typeof unparsed !== 'string') {
6
+ throw new Error('CommitResponse should be a string here.')
7
+ }
8
+
9
+ const cleaned = unparsed.replace(/```json/g, '').replace(/```/g, '')
10
+ const parsed = JSON.parse(cleaned) as GringowCacheItem
11
+ return parsed
12
+ }
@@ -0,0 +1,82 @@
1
+ import {
2
+ env,
3
+ type ProgressInfo,
4
+ pipeline,
5
+ type TranslationPipeline,
6
+ type TranslationSingle,
7
+ } from '@huggingface/transformers'
8
+ import 'node:fs'
9
+
10
+ import { awaitedConfig } from '@/config'
11
+ import { languagesMap } from './languagesMap'
12
+
13
+ import type { AskHuggingfaceResponse, AskHuggingfaceResponseTranslation } from '@/io/huggingface'
14
+
15
+ function getLanguageCode(language: string) {
16
+ return languagesMap[language as keyof typeof languagesMap]
17
+ }
18
+
19
+ function progressCallback(progress: ProgressInfo) {
20
+ process.stdout.clearLine(0)
21
+ process.stdout.cursorTo(0)
22
+ if (progress.status === 'download') {
23
+ process.stdout.write('Downloading...')
24
+ } else if (progress.status === 'initiate') {
25
+ process.stdout.write('Initializing...')
26
+ } else if (progress.status === 'done') {
27
+ process.stdout.write('Done!')
28
+ } else if (progress.status === 'progress' && 'progress' in progress && 'loaded' in progress && 'total' in progress) {
29
+ process.stdout.write(
30
+ `Downloading: ${Number(progress.progress).toFixed(2)}%, loaded: ${progress.loaded}/${progress.total}`
31
+ )
32
+ }
33
+ }
34
+
35
+ async function pipeLanguage(pipe: TranslationPipeline, original: string, srcLanguage: string, targetLanguage: string) {
36
+ const response = await pipe(original, {
37
+ src_lang: srcLanguage,
38
+ tgt_lang: targetLanguage,
39
+ temperature: 1.0,
40
+ } as Parameters<TranslationPipeline['call']>[1])
41
+
42
+ return response
43
+ }
44
+
45
+ export async function askHuggingface(original: string) {
46
+ const config = await awaitedConfig
47
+ const cacheDir = `${config.cache.dir}/.cache`
48
+ env.cacheDir = cacheDir
49
+
50
+ const pipe = await pipeline('translation', config.llm.model as string, {
51
+ progress_callback: progressCallback,
52
+ cache_dir: cacheDir,
53
+ })
54
+
55
+ const { languages } = config
56
+
57
+ let responses: AskHuggingfaceResponseTranslation[] = []
58
+ for await (const language of languages) {
59
+ const response = await pipeLanguage(
60
+ pipe,
61
+ original,
62
+ getLanguageCode(config.sourceLanguage),
63
+ getLanguageCode(language)
64
+ )
65
+
66
+ if (Array.isArray(response)) {
67
+ responses = responses.concat(
68
+ response.map((r) => ({
69
+ ...r,
70
+ targetLanguage: language,
71
+ })) as AskHuggingfaceResponseTranslation[]
72
+ )
73
+ } else {
74
+ responses.push({
75
+ ...(response as TranslationSingle),
76
+ targetLanguage: language,
77
+ } as AskHuggingfaceResponseTranslation)
78
+ }
79
+ }
80
+
81
+ return { responses, original } as AskHuggingfaceResponse
82
+ }
@@ -0,0 +1,16 @@
1
+ import type { GringowTranslation } from '@/index'
2
+ import type { AskHuggingfaceResponse } from '@/io/huggingface'
3
+ import type { CommitResponse } from '@/types'
4
+
5
+ export function commitResponseHuggingface(response: AskHuggingfaceResponse) {
6
+ const { responses, original } = response
7
+ const translation = responses.reduce((acc, curr) => {
8
+ acc[curr.targetLanguage] = curr.translation_text
9
+ return acc
10
+ }, {} as GringowTranslation)
11
+
12
+ return {
13
+ translation,
14
+ original,
15
+ } as CommitResponse
16
+ }
@@ -0,0 +1,16 @@
1
+ import type { TranslationSingle } from '@huggingface/transformers'
2
+
3
+ export type AskHuggingfaceResponseTranslation = TranslationSingle & {
4
+ targetLanguage: string
5
+ }
6
+
7
+ export type AskHuggingfaceResponse = {
8
+ responses: AskHuggingfaceResponseTranslation[]
9
+ original: string
10
+ }
11
+
12
+ export type AskHuggingfaceResult = string
13
+
14
+ export * from './ask'
15
+ export * from './commit-response'
16
+ export * from './parse'
@@ -0,0 +1,189 @@
1
+ export const languagesMap = {
2
+ 'en-US': 'eng_Latn',
3
+ 'en-GB': 'eng_Latn',
4
+ 'pt-BR': 'por_Latn',
5
+ 'pt-PT': 'por_Latn',
6
+ 'es-ES': 'spa_Latn',
7
+ 'es-MX': 'spa_Latn',
8
+ 'fr-FR': 'fra_Latn',
9
+ 'fr-CA': 'fra_Latn',
10
+ 'de-DE': 'deu_Latn',
11
+ 'it-IT': 'ita_Latn',
12
+ 'nl-NL': 'nld_Latn',
13
+ 'pl-PL': 'pol_Latn',
14
+ 'ru-RU': 'rus_Cyrl',
15
+ 'uk-UA': 'ukr_Cyrl',
16
+ 'bg-BG': 'bul_Cyrl',
17
+ 'hr-HR': 'hrv_Latn',
18
+ 'cs-CZ': 'ces_Latn',
19
+ 'sk-SK': 'slk_Latn',
20
+ 'hu-HU': 'hun_Latn',
21
+ 'ro-RO': 'ron_Latn',
22
+ 'fi-FI': 'fin_Latn',
23
+ 'sv-SE': 'swe_Latn',
24
+ 'da-DK': 'dan_Latn',
25
+ 'no-NO': 'nob_Latn',
26
+ 'is-IS': 'isl_Latn',
27
+ 'et-EE': 'est_Latn',
28
+ 'lv-LV': 'lav_Latn',
29
+ 'lt-LT': 'lit_Latn',
30
+ 'mt-MT': 'mlt_Latn',
31
+ 'ga-IE': 'gle_Latn',
32
+ 'cy-GB': 'cym_Latn',
33
+ 'eu-ES': 'eus_Latn',
34
+ 'ca-ES': 'cat_Latn',
35
+ 'gl-ES': 'glg_Latn',
36
+ 'oc-FR': 'oci_Latn',
37
+ 'br-FR': 'bre_Latn',
38
+ 'gd-GB': 'gla_Latn',
39
+ 'kw-GB': 'cor_Latn',
40
+ 'fur-IT': 'fur_Latn',
41
+ 'rm-CH': 'roh_Latn',
42
+ 'lb-LU': 'ltz_Latn',
43
+ 'fy-NL': 'fry_Latn',
44
+ 'af-ZA': 'afr_Latn',
45
+
46
+ 'zh-CN': 'zho_Hans',
47
+ 'zh-TW': 'zho_Hant',
48
+ 'zh-HK': 'zho_Hant',
49
+ 'ja-JP': 'jpn_Jpan',
50
+ 'ko-KR': 'kor_Hang',
51
+ 'th-TH': 'tha_Thai',
52
+ 'vi-VN': 'vie_Latn',
53
+ 'id-ID': 'ind_Latn',
54
+ 'ms-MY': 'zsm_Latn',
55
+ 'tl-PH': 'tgl_Latn',
56
+ 'my-MM': 'mya_Mymr',
57
+ 'km-KH': 'khm_Khmr',
58
+ 'lo-LA': 'lao_Laoo',
59
+ 'bn-BD': 'ben_Beng',
60
+ 'bn-IN': 'ben_Beng',
61
+ 'hi-IN': 'hin_Deva',
62
+ 'ur-PK': 'urd_Arab',
63
+ 'ur-IN': 'urd_Arab',
64
+ 'pa-IN': 'pan_Guru',
65
+ 'gu-IN': 'guj_Gujr',
66
+ 'or-IN': 'ory_Orya',
67
+ 'as-IN': 'asm_Beng',
68
+ 'ml-IN': 'mal_Mlym',
69
+ 'ta-IN': 'tam_Tamil',
70
+ 'te-IN': 'tel_Telu',
71
+ 'kn-IN': 'kan_Knda',
72
+ 'si-LK': 'sin_Sinh',
73
+ 'ne-NP': 'nep_Deva',
74
+ 'dz-BT': 'dzo_Tibt',
75
+ 'bo-CN': 'bod_Tibt',
76
+ 'mn-MN': 'khk_Cyrl',
77
+ 'ka-GE': 'kat_Geor',
78
+ 'hy-AM': 'hye_Armn',
79
+ 'az-AZ': 'azj_Latn',
80
+ 'tr-TR': 'tur_Latn',
81
+ 'fa-IR': 'pes_Arab',
82
+ 'fa-AF': 'pes_Arab',
83
+ 'ps-AF': 'pbt_Arab',
84
+ 'ku-TR': 'kmr_Latn',
85
+ 'he-IL': 'heb_Hebr',
86
+ 'ar-SA': 'arb_Arab',
87
+ 'ar-EG': 'arb_Arab',
88
+ 'ar-MA': 'arb_Arab',
89
+ 'ar-DZ': 'arb_Arab',
90
+ 'ar-TN': 'arb_Arab',
91
+ 'ar-LY': 'arb_Arab',
92
+ 'ar-SD': 'arb_Arab',
93
+ 'ar-IQ': 'arb_Arab',
94
+ 'ar-SY': 'arb_Arab',
95
+ 'ar-JO': 'arb_Arab',
96
+ 'ar-LB': 'arb_Arab',
97
+ 'ar-KW': 'arb_Arab',
98
+ 'ar-AE': 'arb_Arab',
99
+ 'ar-QA': 'arb_Arab',
100
+ 'ar-BH': 'arb_Arab',
101
+ 'ar-OM': 'arb_Arab',
102
+ 'ar-YE': 'arb_Arab',
103
+ 'ar-SO': 'arb_Arab',
104
+ 'ar-DJ': 'arb_Arab',
105
+ 'ar-KM': 'arb_Arab',
106
+ 'ar-TD': 'arb_Arab',
107
+ 'ar-ER': 'arb_Arab',
108
+ 'ar-IL': 'arb_Arab',
109
+ 'ar-PS': 'arb_Arab',
110
+ 'ar-001': 'arb_Arab',
111
+
112
+ 'sw-KE': 'swh_Latn',
113
+ 'sw-TZ': 'swh_Latn',
114
+ 'am-ET': 'amh_Ethi',
115
+ 'om-ET': 'gaz_Latn',
116
+ 'so-SO': 'som_Latn',
117
+ 'ha-NG': 'hau_Latn',
118
+ 'yo-NG': 'yor_Latn',
119
+ 'ig-NG': 'ibo_Latn',
120
+ 'zu-ZA': 'zul_Latn',
121
+ 'xh-ZA': 'xho_Latn',
122
+ 'rw-RW': 'kin_Latn',
123
+ 'lg-UG': 'lug_Latn',
124
+ 'ak-GH': 'aka_Latn',
125
+ 'tw-GH': 'twi_Latn',
126
+ 'ee-GH': 'ewe_Latn',
127
+ 'ff-SN': 'fuv_Latn',
128
+ 'wo-SN': 'wol_Latn',
129
+ 'sn-ZW': 'sna_Latn',
130
+ 'nd-ZW': 'nde_Latn',
131
+ 'ny-MW': 'nya_Latn',
132
+ 'st-ZA': 'sot_Latn',
133
+ 'tn-BW': 'tsn_Latn',
134
+ 'ts-ZA': 'tso_Latn',
135
+ 've-ZA': 'ven_Latn',
136
+ 'ss-ZA': 'ssw_Latn',
137
+ 'nr-ZA': 'nbl_Latn',
138
+
139
+ 'qu-PE': 'quy_Latn',
140
+ 'qu-BO': 'quy_Latn',
141
+ 'qu-EC': 'quy_Latn',
142
+ 'ay-BO': 'aym_Latn',
143
+ 'gn-PY': 'grn_Latn',
144
+ 'ht-HT': 'hat_Latn',
145
+ 'jv-ID': 'jav_Latn',
146
+ 'su-ID': 'sun_Latn',
147
+ 'ceb-PH': 'ceb_Latn',
148
+ 'ilo-PH': 'ilo_Latn',
149
+ 'war-PH': 'war_Latn',
150
+ 'bjn-ID': 'bjn_Latn',
151
+ 'ban-ID': 'ban_Latn',
152
+ 'mad-ID': 'mad_Latn',
153
+ 'min-ID': 'min_Latn',
154
+ 'ace-ID': 'ace_Latn',
155
+ 'bug-ID': 'bug_Latn',
156
+ 'gor-ID': 'gor_Latn',
157
+ 'mak-ID': 'mak_Latn',
158
+ 'mdr-ID': 'mdr_Latn',
159
+ 'sas-ID': 'sas_Latn',
160
+ 'shn-MM': 'shn_Mymr',
161
+ 'kac-MM': 'kac_Latn',
162
+ 'lzh-CN': 'lzh_Hant',
163
+ 'wuu-CN': 'wuu_Hans',
164
+ 'yue-CN': 'yue_Hant',
165
+ 'nan-TW': 'nan_Hant',
166
+ 'hak-TW': 'hak_Hant',
167
+ 'cjy-CN': 'cjy_Hans',
168
+ 'cmn-CN': 'cmn_Hans',
169
+ 'cmn-TW': 'cmn_Hant',
170
+ 'cmn-SG': 'cmn_Hans',
171
+ 'cmn-HK': 'cmn_Hant',
172
+ 'cmn-MO': 'cmn_Hant',
173
+ 'cmn-001': 'cmn_Hans',
174
+
175
+ 'haw-US': 'haw_Latn',
176
+ 'mi-NZ': 'mri_Latn',
177
+ 'sm-WS': 'smo_Latn',
178
+ 'to-TO': 'ton_Latn',
179
+ 'fj-FJ': 'fij_Latn',
180
+ 'ty-PF': 'tah_Latn',
181
+ 'co-FR': 'cos_Latn',
182
+ 'eu-FR': 'eus_Latn',
183
+
184
+ 'kk-KZ': 'kaz_Cyrl',
185
+ 'ky-KG': 'kir_Cyrl',
186
+ 'uz-UZ': 'uzn_Latn',
187
+ 'tg-TJ': 'tgk_Cyrl',
188
+ 'tk-TM': 'tuk_Latn',
189
+ }
@@ -0,0 +1,10 @@
1
+ import type { GringowCacheItem } from '@/index'
2
+ import type { CommitResponse } from '@/types'
3
+
4
+ export function parseHuggingface(unparsed: CommitResponse): GringowCacheItem {
5
+ if (typeof unparsed === 'string') {
6
+ throw new Error('CommitResponse should not be a string here.')
7
+ }
8
+
9
+ return unparsed
10
+ }
package/src/logger.ts ADDED
@@ -0,0 +1,25 @@
1
+ // "error", "warn", "info", "debug", "trace"
2
+ const levels = {
3
+ error: 0,
4
+ warn: 1,
5
+ info: 2,
6
+ debug: 3,
7
+ trace: 4,
8
+ }
9
+
10
+ type Level = keyof typeof levels
11
+
12
+ const currentLevel: Level = 'info'
13
+ const maxLevel = levels[currentLevel]
14
+
15
+ const noop = () => void 0
16
+
17
+ const logger = {
18
+ error: maxLevel >= levels.error ? console.error : noop,
19
+ warn: maxLevel >= levels.warn ? console.warn : noop,
20
+ info: maxLevel >= levels.info ? console.info : noop,
21
+ debug: maxLevel >= levels.debug ? console.debug : noop,
22
+ trace: maxLevel >= levels.trace ? console.trace : noop,
23
+ }
24
+
25
+ export default logger
@@ -0,0 +1,58 @@
1
+ import { z } from 'zod'
2
+
3
+ export const languageSchema = z.string().regex(/^[a-z]{2,3}(-[A-Z]{2})?$/)
4
+
5
+ export const llmOptionsSchema = z.enum(['gemini', 'anthropic', 'huggingface'])
6
+
7
+ export const configSchema = z
8
+ .object({
9
+ debug: z.boolean().optional(),
10
+ log: z.object({
11
+ level: z.enum(['debug', 'info', 'warn', 'error', 'trace']),
12
+ }),
13
+ cache: z.object({
14
+ dir: z.string(),
15
+ file: z.string(),
16
+ prefix: z.string(),
17
+ write: z.boolean().optional(),
18
+ }),
19
+ keys: z.object({
20
+ anthropicApiKey: z.string().optional(),
21
+ geminiApiKey: z.string().optional(),
22
+ }),
23
+ sourceLanguage: languageSchema,
24
+ languages: z.array(languageSchema),
25
+ llm: z.object({
26
+ choice: llmOptionsSchema,
27
+ model: z.string().nullable(),
28
+ temperature: z.number().optional(),
29
+ sleep: z.number().optional(),
30
+ prePrompt: z.string().optional(),
31
+ postPrompt: z.string().optional(),
32
+ prePromptReplace: z.string().optional(),
33
+ postPromptReplace: z.string().optional(),
34
+ prePromptAppend: z.string().optional(),
35
+ postPromptAppend: z.string().optional(),
36
+ }),
37
+ globby: z
38
+ .string()
39
+ .regex(/^\.\/.*/)
40
+ .optional(),
41
+ })
42
+ .refine(
43
+ (data) => {
44
+ if (data.llm.choice === 'gemini' && !data.keys.geminiApiKey) {
45
+ return false
46
+ }
47
+
48
+ if (data.llm.choice === 'anthropic' && !data.keys.anthropicApiKey) {
49
+ return false
50
+ }
51
+
52
+ return true
53
+ },
54
+ {
55
+ message: 'The API key corresponding to the chosen LLM must be present.',
56
+ path: ['keys'],
57
+ }
58
+ )
@@ -0,0 +1,22 @@
1
+ import type { z } from 'zod'
2
+ import type { GringowCacheItem, GringowTranslation } from '@/index'
3
+ import type { llmOptionsSchema } from '@/schemas/config.shcema'
4
+
5
+ export type CommitResponse = string | { translation: GringowTranslation; original: string }
6
+
7
+ export type Asker<T> = (message: string) => Promise<T>
8
+ export type Commiter<T> = (response: T) => CommitResponse
9
+ export type Parser = (unparsed: CommitResponse) => GringowCacheItem
10
+
11
+ export type LLMOptions = z.infer<typeof llmOptionsSchema>
12
+
13
+ export type LLMChoice<T> = {
14
+ ask: Asker<T>
15
+ commitResponse: Commiter<T>
16
+ parse: Parser
17
+ }
18
+
19
+ export type LLMChoices = {
20
+ // biome-ignore lint/suspicious/noExplicitAny: LLMChoice can receive any type
21
+ [K in LLMOptions]: LLMChoice<any>
22
+ }