/** * OpenAI: nahrani souboru. * * Sluzba: https://api.openai.com/v1 * Endpoint: POST /files (multipart/form-data) * * Vzor **odeslani souboru**. Obsah prichazi jako Base64, protoze parametr kroku * je vzdy hodnota zapsatelna do JSONu - binarni data by se do stromu ani do * zaznamu behu jinak nevesla. Prevod na bajty a slozeni multipart tela dela * `ctx.http.postForm`, skript o hranicich ani o hlavickach nevi. * * Vraci ID souboru. Otazku nad nim polozi `openai.ask-about-file`. */ export const manifest = { id: 'openai.upload-file', name: 'Nahrát soubor', description: 'Odešle soubor do OpenAI a vrátí jeho ID. To se pak použije jako vstup ' + 'operace Zeptat se na soubor.', inputs: [ { id: 'fileName', label: 'Název souboru', type: 'string', required: true, hint: 'Včetně přípony, například smlouva.pdf. Podle ní se pozná typ obsahu.', }, { id: 'contentBase64', label: 'Obsah souboru (Base64)', type: 'string', required: true, multiline: true, hint: 'Obsah zakódovaný v Base64. Bez předpony data:.', }, { id: 'contentType', label: 'Typ obsahu', type: 'string', required: false, hint: 'Například application/pdf. Bez vyplnění se odvodí z přípony.', }, { id: 'purpose', label: 'K čemu soubor je', type: 'string', required: false, default: 'user_data', options: [ { value: 'user_data', label: 'Podklad k dotazu' }, { value: 'assistants', label: 'Asistenti a vyhledávání v souborech' }, { value: 'vision', label: 'Obrázek k analýze' }, { value: 'batch', label: 'Dávkové zpracování' }, ], hint: 'OpenAI podle toho rozhoduje, kde se soubor smí použít.', }, ], outputs: [ { id: 'fileId', label: 'ID souboru', type: 'string', required: true }, { id: 'fileName', label: 'Název souboru', type: 'string', required: true }, { id: 'bytes', label: 'Velikost v bajtech', type: 'number', required: false }, { id: 'purpose', label: 'K čemu soubor je', type: 'string', required: false }, ], timeoutMs: 60000, }; /** Typ obsahu podle pripony. Jen to, co se v automatizaci opravdu posila. */ const byExtension = { pdf: 'application/pdf', txt: 'text/plain', md: 'text/markdown', csv: 'text/csv', json: 'application/json', html: 'text/html', docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation', png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', webp: 'image/webp', gif: 'image/gif', }; export async function run(inputs, ctx) { const { pick, text, num, need } = ctx.util; const extension = inputs.fileName.split('.').pop()?.toLowerCase() ?? ''; const contentType = inputs.contentType ?? byExtension[extension] ?? 'application/octet-stream'; // Prazdny obsah se pozna tady, ne az podle nicnerikajici chyby od OpenAI. if (inputs.contentBase64.trim() === '') ctx.fail('Obsah souboru je prázdný.'); const { body } = await ctx.http.postForm('/files', { purpose: inputs.purpose, file: { filename: inputs.fileName, base64: inputs.contentBase64.trim(), contentType, }, }); ctx.log(`Nahráno ${inputs.fileName} jako ${contentType}.`); return { fileId: need(text(pick(body, 'id')), 'ID souboru'), fileName: need(text(pick(body, 'filename')) ?? inputs.fileName, 'název souboru'), bytes: num(pick(body, 'bytes')), purpose: text(pick(body, 'purpose')), }; }