Node.js
npm install axiosimport axios from 'axios';import fs from 'node:fs';import path from 'node:path';import { randomUUID } from 'node:crypto';
const API_KEY = 'sk_your_key';const BASE = 'https://omnidoc.top/api/v1';const API_HEADERS = { 'X-API-Key': API_KEY };
async function uploadDocument(filePath: string) { const stat = fs.statSync(filePath); const { data: ticket } = await axios.post( `${BASE}/upload/presign`, { filename: path.basename(filePath), file_size: stat.size, }, { headers: API_HEADERS, timeout: 20_000 } );
await axios.put(ticket.upload_url, fs.createReadStream(filePath), { headers: { ...ticket.upload_headers, 'Content-Length': stat.size, }, maxBodyLength: Infinity, timeout: 300_000, });
const { data: finalized } = await axios.post( `${BASE}/upload/finalize`, { finalize_token: ticket.finalize_token }, { headers: API_HEADERS, timeout: 30_000 } ); return finalized.document;}
async function createJob(documentId: string, targetLang: string, engine: string) { const { data } = await axios.post( `${BASE}/jobs`, { document_id: documentId, target_lang: targetLang, engine, }, { headers: { ...API_HEADERS, 'Idempotency-Key': randomUUID() }, timeout: 30_000, } ); return data;}
async function waitForJob(jobId: string) { while (true) { const { data } = await axios.get(`${BASE}/jobs/${jobId}`, { headers: API_HEADERS, timeout: 20_000, }); if (data.status === 'completed') return data; if (data.status === 'failed') { throw new Error(data.error_msg || 'translation failed'); } await new Promise((resolve) => setTimeout(resolve, 3000)); }}
function responseFilename(contentDisposition: string | undefined, fallback: string) { const utf8 = contentDisposition?.match(/filename\*=UTF-8''([^;]+)/i); if (utf8) return path.basename(decodeURIComponent(utf8[1])); const asciiName = contentDisposition?.match(/filename="([^"]+)"/i); return asciiName ? path.basename(asciiName[1]) : fallback;}
async function downloadArtifacts(jobId: string, outputDir: string) { fs.mkdirSync(outputDir, { recursive: true }); const { data: artifacts } = await axios.get(`${BASE}/artifacts`, { headers: API_HEADERS, params: { job_id: jobId }, timeout: 20_000, });
const outputs: string[] = []; for (const artifact of artifacts) { const response = await axios.get( `${BASE}/artifacts/${artifact.id}/download`, { headers: API_HEADERS, responseType: 'arraybuffer', timeout: 300_000, } ); const filename = responseFilename( response.headers['content-disposition'], `${artifact.id}.${artifact.format}` ); const destination = path.join(outputDir, filename); fs.writeFileSync(destination, response.data); outputs.push(destination); } return outputs;}
async function translateDocument(filePath: string, targetLang = 'en') { const document = await uploadDocument(filePath); const job = await createJob(document.id, targetLang, 'xr-397b'); await waitForJob(job.id); return downloadArtifacts(job.id, 'omnidoc-output');}
translateDocument('report.docx').then(console.log).catch(console.error);浏览器与 Node.js 的差异
Section titled “浏览器与 Node.js 的差异”浏览器会根据 File 自动设置 Content-Length;Node.js 上传文件流时应显式设置准确值。
两端都必须逐项发送 presign 返回的 upload_headers,且不要把 X-API-Key 发到上传 URL。