import { readFileSync } from "node:fs"; import path from "node:path"; import type { SupportedLanguageCode, SupportedPos } from "@lila/shared"; export const LANGUAGE_NAMES: Record = { en: "English", it: "Italian", de: "German", fr: "French", es: "Spanish", }; export type PromptParams = { sourceLanguage: SupportedLanguageCode; pos: SupportedPos; targetLanguages: readonly SupportedLanguageCode[]; words: readonly string[]; }; export const loadPromptTemplate = (): string => readFileSync(path.join(import.meta.dirname, "prompt"), "utf-8"); export const renderPrompt = ( template: string, params: PromptParams, ): string => { const { sourceLanguage, pos, targetLanguages, words } = params; if (words.length === 0) { throw new Error("renderPrompt: empty word batch"); } if ( targetLanguages.length === 0 || targetLanguages.includes(sourceLanguage) ) { throw new Error( `renderPrompt: target languages must be non-empty and exclude the source language (got ${targetLanguages.join(", ")})`, ); } const rendered = template .replaceAll("{{SOURCE_LANGUAGE_NAME}}", LANGUAGE_NAMES[sourceLanguage]) .replaceAll("{{SOURCE_LANGUAGE_CODE}}", sourceLanguage) .replaceAll("{{POS}}", pos) .replaceAll("{{TARGET_LANGUAGE_CODES}}", targetLanguages.join(", ")) .replaceAll( "{{TARGET_LANGUAGE_UNION}}", targetLanguages.map((code) => `"${code}"`).join(" | "), ) .replaceAll("{{INPUT_WORDS}}", words.join("\n")); const leftover = rendered.match(/\{\{[A-Z_]+\}\}/); if (leftover) { throw new Error(`renderPrompt: unreplaced placeholder ${leftover[0]}`); } return rendered; };