- Admin panel (/admin) with JWT auth: configure Replicate API token, JigsawStack API key, model version, enable/disable AI translation, change admin password. Settings persisted in data/settings.json. - Replicate AI translation: POST /api/translate/replicate uses JigsawStack text-translate model via Replicate API. Main page switches to client-side AI translation when enabled. - Document translation tab: supports PDF, DOCX, XLSX, XLS, CSV. Excel/Word formatting fully preserved (SheetJS + JSZip XML manipulation). PDF uses pdf-parse extraction + pdf-lib reconstruction. Column selector UI for tabular data (per-sheet, All/None toggles). - Updated README with full implementation documentation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
84 lines
2.5 KiB
TypeScript
84 lines
2.5 KiB
TypeScript
import { readSettings } from "./settings-store";
|
|
|
|
type ReplicateOutput = string | string[] | { translation?: string; translated_text?: string; output?: string };
|
|
|
|
export async function replicateTranslate(
|
|
text: string,
|
|
targetLanguage: string
|
|
): Promise<string> {
|
|
const settings = readSettings();
|
|
|
|
if (!settings.replicateApiToken) {
|
|
throw new Error("Replicate API token not configured");
|
|
}
|
|
if (!settings.jigsawApiKey) {
|
|
throw new Error("JigsawStack API key not configured");
|
|
}
|
|
|
|
const body = {
|
|
version: settings.modelVersion,
|
|
input: {
|
|
text,
|
|
api_key: settings.jigsawApiKey,
|
|
target_language: targetLanguage
|
|
}
|
|
};
|
|
|
|
const response = await fetch("https://api.replicate.com/v1/predictions", {
|
|
method: "POST",
|
|
headers: {
|
|
"Authorization": `Bearer ${settings.replicateApiToken}`,
|
|
"Content-Type": "application/json",
|
|
"Prefer": "wait"
|
|
},
|
|
body: JSON.stringify(body)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const err = await response.text();
|
|
throw new Error(`Replicate API error: ${response.status} ${err}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.error) {
|
|
throw new Error(`Replicate model error: ${data.error}`);
|
|
}
|
|
|
|
// Extract translated text from various output formats
|
|
const output: ReplicateOutput = data.output;
|
|
|
|
if (typeof output === "string") return output;
|
|
if (Array.isArray(output)) return output.join("");
|
|
if (output && typeof output === "object") {
|
|
return output.translation ?? output.translated_text ?? output.output ?? String(output);
|
|
}
|
|
|
|
throw new Error("Unexpected output format from Replicate");
|
|
}
|
|
|
|
// Batch translate using separator trick to minimize API calls
|
|
const SEPARATOR = "\n{{SEP}}\n";
|
|
|
|
export async function replicateTranslateBatch(
|
|
texts: string[],
|
|
targetLanguage: string
|
|
): Promise<string[]> {
|
|
if (texts.length === 0) return [];
|
|
if (texts.length === 1) {
|
|
return [await replicateTranslate(texts[0], targetLanguage)];
|
|
}
|
|
|
|
const joined = texts.join(SEPARATOR);
|
|
const translated = await replicateTranslate(joined, targetLanguage);
|
|
|
|
// Try to split on the separator; fall back to individual calls if it got translated
|
|
const parts = translated.split(SEPARATOR);
|
|
if (parts.length === texts.length) {
|
|
return parts;
|
|
}
|
|
|
|
// Fallback: translate individually
|
|
return Promise.all(texts.map(t => replicateTranslate(t, targetLanguage)));
|
|
}
|