nothumanallowed 13.2.70 → 13.2.72
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.
- package/package.json +1 -1
- package/src/commands/ui.mjs +111 -9
- package/src/constants.mjs +1 -1
- package/src/services/web-ui.mjs +44 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "13.2.
|
|
3
|
+
"version": "13.2.72",
|
|
4
4
|
"description": "NotHumanAllowed — 38 AI agents, 80 tools, Studio (visual agentic workflows). Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, Alexandria E2E messaging, GitHub, Notion, Slack, voice chat, free AI (Liara), 28 languages. Zero-dependency CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/commands/ui.mjs
CHANGED
|
@@ -2692,9 +2692,10 @@ export async function cmdUI(args) {
|
|
|
2692
2692
|
|
|
2693
2693
|
// ── Fast keyword-based planning (no LLM call needed for common patterns) ──────
|
|
2694
2694
|
const taskLow = task.toLowerCase();
|
|
2695
|
+
const hasPdf = !!(body.hasPdf) || /pdf|allegat|catalogo|scheda\s*tecnic|document/i.test(taskLow);
|
|
2695
2696
|
const hasEmail = /email|mail|inbox|posta/i.test(taskLow);
|
|
2696
2697
|
const hasCalendar = /calendar|agenda|calendari|eventi|schedule/i.test(taskLow);
|
|
2697
|
-
const hasSearch = /cerca|search|notizie|news|ultime|latest|web|internet|tendenz|trend/i.test(taskLow);
|
|
2698
|
+
const hasSearch = /cerca|search|notizie|news|ultime|latest|web|internet|tendenz|trend|acquista|compra|dove\s+trovare|where\s+to\s+buy|similar|simile/i.test(taskLow);
|
|
2698
2699
|
const hasCanvas = /html|dashboard|visua|report|grafico|chart/i.test(taskLow);
|
|
2699
2700
|
const hasGitHub = /github|git|issue|pr|pull request/i.test(taskLow);
|
|
2700
2701
|
const hasSlack = /slack|channel|messag/i.test(taskLow);
|
|
@@ -2726,13 +2727,23 @@ export async function cmdUI(args) {
|
|
|
2726
2727
|
// Build plan directly from keywords — reliable, fast, no SENTINEL risk
|
|
2727
2728
|
const buildKeywordPlan = () => {
|
|
2728
2729
|
const steps = [];
|
|
2730
|
+
// PDF attachment: always read document first to extract specs/data before any web search
|
|
2731
|
+
if (hasPdf) {
|
|
2732
|
+
const pdfName = body.pdfName || 'documento allegato';
|
|
2733
|
+
steps.push({icon:'\u{1F4C4}',agent:'DocumentReaderAgent',label:it?'Leggi documento':'Read document',prompt:`Extract all technical specifications, model numbers, part codes, product names, manufacturer, dimensions, ratings, and any other key data from the attached document "${pdfName}". List every technical detail precisely.`});
|
|
2734
|
+
}
|
|
2729
2735
|
if (hasEmail) steps.push({icon:'\u{1F4E7}',agent:'EmailAgent', label:it?'Controlla email':'Check emails', prompt:'Read the latest unread emails and identify urgent items, deadlines, and required actions'});
|
|
2730
2736
|
if (hasCalendar) steps.push({icon:'\u{1F4C5}',agent:'CalendarAgent', label:it?'Rivedi calendario':'Review calendar', prompt:'Check today\'s events and identify any scheduling conflicts or important meetings'});
|
|
2731
2737
|
if (hasGitHub) steps.push({icon:'\u{1F4BB}',agent:'GitHubAgent', label:'GitHub', prompt:'Read open issues and pull requests, identify what needs attention'});
|
|
2732
2738
|
if (hasSlack) steps.push({icon:'\u{1F4AC}',agent:'SlackAgent', label:'Slack', prompt:'Check recent Slack messages and identify important conversations'});
|
|
2733
2739
|
if (hasNotion) steps.push({icon:'\u{1F4DD}',agent:'NotionAgent', label:'Notion', prompt:'Search Notion for relevant pages and notes'});
|
|
2734
|
-
|
|
2735
|
-
|
|
2740
|
+
// When PDF is present: always search web (to find where to buy, similar products etc.)
|
|
2741
|
+
// The search query will be refined at runtime using the extracted PDF specs as context
|
|
2742
|
+
if (hasPdf || hasSearch || hasReputation || (!hasEmail && !hasCalendar && !hasGitHub && !hasSlack)) {
|
|
2743
|
+
const searchPrompt = hasPdf
|
|
2744
|
+
? (it ? 'Usando le specifiche tecniche estratte dal documento (codice prodotto, modello, costruttore, caratteristiche), cerca online dove acquistare il prodotto o articoli equivalenti. Usa i codici esatti dal documento come query di ricerca.' : 'Using the technical specifications extracted from the document (product code, model, manufacturer, specs), search online for where to buy this product or equivalent alternatives. Use exact codes from the document as search queries.')
|
|
2745
|
+
: searchQuery;
|
|
2746
|
+
steps.push({icon:'\u{1F50D}',agent:'WebSearchAgent',label:it?'Ricerca web':'Web search',prompt:searchPrompt});
|
|
2736
2747
|
}
|
|
2737
2748
|
// Specialist agents — can stack multiple
|
|
2738
2749
|
if (hasSecurity) steps.push({icon:'\u{1F6E1}',agent:'cassandra', label:it?'CASSANDRA \u2014 Rischi sicurezza':'CASSANDRA \u2014 Security risks', prompt:'Analyze the collected data and identify security risks, vulnerabilities and concrete recommendations'});
|
|
@@ -2758,7 +2769,7 @@ export async function cmdUI(args) {
|
|
|
2758
2769
|
|
|
2759
2770
|
// Use keyword plan directly — only fall back to LLM for genuinely ambiguous tasks
|
|
2760
2771
|
const keywordSteps = buildKeywordPlan();
|
|
2761
|
-
const taskIsComplex = !hasEmail && !hasCalendar && !hasSearch && !hasGitHub && !hasSlack && !hasBriefing && !hasStrategy && !hasReputation && !hasCode && !hasWriting && !hasData && keywordSteps.length <= 1;
|
|
2772
|
+
const taskIsComplex = !hasPdf && !hasEmail && !hasCalendar && !hasSearch && !hasGitHub && !hasSlack && !hasBriefing && !hasStrategy && !hasReputation && !hasCode && !hasWriting && !hasData && keywordSteps.length <= 1;
|
|
2762
2773
|
|
|
2763
2774
|
try {
|
|
2764
2775
|
let steps;
|
|
@@ -2805,6 +2816,10 @@ export async function cmdUI(args) {
|
|
|
2805
2816
|
if (pathname === '/api/studio/run' && method === 'POST') {
|
|
2806
2817
|
const body = await parseBody(req);
|
|
2807
2818
|
const { agent, task, context, stepDef } = body;
|
|
2819
|
+
const stepPdfBase64 = body.pdfBase64 || null;
|
|
2820
|
+
const stepPdfName = body.pdfName || null;
|
|
2821
|
+
const stepImageBase64 = body.imageBase64 || null;
|
|
2822
|
+
const stepImageMime = body.imageMimeType || 'image/jpeg';
|
|
2808
2823
|
if (!agent || !task) { sendJSON(res, 400, { error: 'agent and task required' }); logRequest(method, pathname, 400, Date.now() - start); return; }
|
|
2809
2824
|
|
|
2810
2825
|
res.writeHead(200, {
|
|
@@ -2836,7 +2851,41 @@ export async function cmdUI(args) {
|
|
|
2836
2851
|
let toolData = '';
|
|
2837
2852
|
|
|
2838
2853
|
// ── Fetch REAL data for each agent type ──────────────────────
|
|
2839
|
-
if (agent === '
|
|
2854
|
+
if (agent === 'DocumentReaderAgent') {
|
|
2855
|
+
// Extract text from attached PDF and return it as the step output.
|
|
2856
|
+
// This becomes context for all subsequent steps (WebSearchAgent etc.)
|
|
2857
|
+
sendToken('[Reading attached document...] ');
|
|
2858
|
+
if (stepPdfBase64) {
|
|
2859
|
+
try {
|
|
2860
|
+
const b64 = stepPdfBase64.includes(',') ? stepPdfBase64.split(',')[1] : stepPdfBase64;
|
|
2861
|
+
const pdfBuffer = Buffer.from(b64, 'base64');
|
|
2862
|
+
const extracted = extractTextFromPdf(pdfBuffer);
|
|
2863
|
+
if (extracted && extracted.length > 20) {
|
|
2864
|
+
toolData = `## Document: ${stepPdfName || 'attached'}\n\n${extracted.slice(0, 20000)}`;
|
|
2865
|
+
} else {
|
|
2866
|
+
// Fallback: ask vision model to describe/OCR the document
|
|
2867
|
+
sendToken('[No text found — using vision OCR...] ');
|
|
2868
|
+
try {
|
|
2869
|
+
const visionText = await callLLMVision(config, stepPdfBase64, 'application/pdf',
|
|
2870
|
+
`Extract ALL text, technical specifications, model numbers, part codes, product names, manufacturer details, dimensions, ratings, and any other data from this document. List every detail exactly as printed.`);
|
|
2871
|
+
toolData = `## Document (OCR): ${stepPdfName || 'attached'}\n\n${visionText}`;
|
|
2872
|
+
} catch (ve) {
|
|
2873
|
+
toolData = `Could not extract text from document: ${ve.message}`;
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
} catch (e) { toolData = `Document read failed: ${e.message}`; }
|
|
2877
|
+
} else {
|
|
2878
|
+
toolData = 'No document attached.';
|
|
2879
|
+
}
|
|
2880
|
+
// Stream the extracted content as the step output directly — no LLM rewrite needed
|
|
2881
|
+
sendToken(toolData);
|
|
2882
|
+
clearInterval(keepalive);
|
|
2883
|
+
sendEvent({ done: true, usage: { input: 0, output: Math.ceil(toolData.length / 4) } });
|
|
2884
|
+
res.end();
|
|
2885
|
+
logRequest(method, pathname, 200, Date.now() - start);
|
|
2886
|
+
return;
|
|
2887
|
+
|
|
2888
|
+
} else if (agent === 'EmailAgent') {
|
|
2840
2889
|
sendToken('[Reading emails...] ');
|
|
2841
2890
|
try {
|
|
2842
2891
|
const emails = await withTimeout(getUnreadImportant(config, 10), 'EmailAgent');
|
|
@@ -2859,7 +2908,23 @@ export async function cmdUI(args) {
|
|
|
2859
2908
|
try {
|
|
2860
2909
|
// Extract a concise search query from the step prompt
|
|
2861
2910
|
let searchQuery = stepPrompt;
|
|
2862
|
-
|
|
2911
|
+
|
|
2912
|
+
// If context contains extracted PDF data, extract the best search query from it:
|
|
2913
|
+
// product codes, model numbers, part numbers etc. are better search terms than the task text.
|
|
2914
|
+
if (context && context.length > 50) {
|
|
2915
|
+
// Look for product codes: alphanumeric codes, e.g. 321k63, VFD-001, 4WE6D6X
|
|
2916
|
+
const codeMatch = context.match(/\b([A-Z0-9]{2,}[-\/]?[A-Z0-9]{2,}(?:[-\/][A-Z0-9]+)*)\b/g);
|
|
2917
|
+
const productCodes = codeMatch ? [...new Set(codeMatch)].slice(0, 3) : [];
|
|
2918
|
+
// Also grab manufacturer name if present
|
|
2919
|
+
const mfrMatch = context.match(/(?:Marca|Marchio|Manufacturer|Brand|Produttore|Costruttore)[:\s]+([A-Za-z0-9 &]{2,40})/i);
|
|
2920
|
+
const mfr = mfrMatch ? mfrMatch[1].trim() : '';
|
|
2921
|
+
if (productCodes.length > 0) {
|
|
2922
|
+
searchQuery = (mfr ? mfr + ' ' : '') + productCodes.join(' ') + ' buy acquista distributore';
|
|
2923
|
+
sendToken(`[Search query from document: "${searchQuery}"] `);
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
|
|
2927
|
+
if (searchQuery.length > 120 && !context) {
|
|
2863
2928
|
const keywordMatch = searchQuery.match(/(?:cerca|search|find|ricerca|notizie su|news about|latest on|aggiornamenti su)\s+(.{5,80}?)(?:\s+(?:e|and|per|for|poi|then)|$)/i);
|
|
2864
2929
|
if (keywordMatch) {
|
|
2865
2930
|
searchQuery = keywordMatch[1].trim();
|
|
@@ -3009,11 +3074,48 @@ RULES:
|
|
|
3009
3074
|
- Use .priority-list for action items, .source-item for each email/news source, .bar-row for any percentage data
|
|
3010
3075
|
- Output must start with <div class="header"> and end with <div class="footer">`;
|
|
3011
3076
|
|
|
3077
|
+
// ── Handle PDF/image attachment on first step ─────────────────
|
|
3078
|
+
let attachmentText = '';
|
|
3079
|
+
if (stepPdfBase64 && !isLiveDataAgent) {
|
|
3080
|
+
sendToken('[Reading PDF...] ');
|
|
3081
|
+
try {
|
|
3082
|
+
// Extract base64 payload (strip data URL prefix if present)
|
|
3083
|
+
const b64 = stepPdfBase64.includes(',') ? stepPdfBase64.split(',')[1] : stepPdfBase64;
|
|
3084
|
+
const pdfBuffer = Buffer.from(b64, 'base64');
|
|
3085
|
+
const extracted = extractTextFromPdf(pdfBuffer);
|
|
3086
|
+
if (extracted && extracted.length > 20) {
|
|
3087
|
+
attachmentText = `## ATTACHED PDF: ${stepPdfName || 'document.pdf'}\n${extracted.slice(0, 15000)}`;
|
|
3088
|
+
} else {
|
|
3089
|
+
// PDF has no extractable text (scanned) — use vision
|
|
3090
|
+
try {
|
|
3091
|
+
const { callLLMVision } = await import('../services/llm.mjs');
|
|
3092
|
+
const visionResult = await withTimeout(
|
|
3093
|
+
callLLMVision(config, 'Extract all text and information from this PDF document.', task, { base64: b64, mimeType: 'application/pdf' }),
|
|
3094
|
+
60000
|
|
3095
|
+
);
|
|
3096
|
+
if (visionResult) attachmentText = `## ATTACHED PDF: ${stepPdfName || 'document.pdf'}\n${visionResult.slice(0, 15000)}`;
|
|
3097
|
+
} catch {}
|
|
3098
|
+
}
|
|
3099
|
+
} catch (e) { attachmentText = `[PDF read failed: ${e.message}]`; }
|
|
3100
|
+
} else if (stepImageBase64 && !isLiveDataAgent) {
|
|
3101
|
+
sendToken('[Reading image...] ');
|
|
3102
|
+
try {
|
|
3103
|
+
const { callLLMVision } = await import('../services/llm.mjs');
|
|
3104
|
+
const b64 = stepImageBase64.includes(',') ? stepImageBase64.split(',')[1] : stepImageBase64;
|
|
3105
|
+
const visionResult = await withTimeout(
|
|
3106
|
+
callLLMVision(config, 'Describe and extract all information from this image in detail.', task, { base64: b64, mimeType: stepImageMime }),
|
|
3107
|
+
45000
|
|
3108
|
+
);
|
|
3109
|
+
if (visionResult) attachmentText = `## ATTACHED IMAGE: ${body.imageName || 'image'}\n${visionResult.slice(0, 8000)}`;
|
|
3110
|
+
} catch (e) { attachmentText = `[Image read failed: ${e.message}]`; }
|
|
3111
|
+
}
|
|
3112
|
+
|
|
3012
3113
|
let sysPrompt, userMsg;
|
|
3013
3114
|
|
|
3014
3115
|
if (isCanvasAgent) {
|
|
3015
3116
|
sysPrompt = canvasSystemPrompt;
|
|
3016
|
-
|
|
3117
|
+
const canvasData = [attachmentText, context].filter(Boolean).join('\n\n');
|
|
3118
|
+
userMsg = `Create a professional dashboard report for this data. Output ONLY the inner HTML body content (starting with <div class="header">):\n\n${canvasData}`;
|
|
3017
3119
|
} else if (isLiveDataAgent) {
|
|
3018
3120
|
// These agents fetched real data — use a focused prompt (no tool definitions to avoid JSON output)
|
|
3019
3121
|
const agentInstruction = `You are ${agent}, a specialist AI agent inside NHA Studio. Today is ${today}. Respond entirely in ${language}.
|
|
@@ -3025,7 +3127,7 @@ CRITICAL: Do NOT invent, hallucinate, or add any data not present in the DATA se
|
|
|
3025
3127
|
Do NOT output JSON, tool calls, or code blocks. Write in plain text with markdown headers.
|
|
3026
3128
|
Always apply your analysis specifically to the subject mentioned in the WORKFLOW GOAL.
|
|
3027
3129
|
|
|
3028
|
-
${toolData ? `## DATA FROM TOOLS:\n${toolData}\n` : '## DATA: No data was retrieved by this agent.\n'}
|
|
3130
|
+
${attachmentText ? `## ATTACHED FILE CONTENT:\n${attachmentText}\n` : ''}${toolData ? `## DATA FROM TOOLS:\n${toolData}\n` : '## DATA: No data was retrieved by this agent.\n'}
|
|
3029
3131
|
${context ? `## OUTPUT FROM PREVIOUS AGENTS:\n${context}\n` : ''}
|
|
3030
3132
|
|
|
3031
3133
|
Your task: ${stepPrompt}`;
|
|
@@ -3052,7 +3154,7 @@ CRITICAL RULES:
|
|
|
3052
3154
|
- Be thorough and specific — this is for an executive briefing based on REAL data only
|
|
3053
3155
|
- Always keep the OVERALL WORKFLOW GOAL in mind — apply your analysis specifically to the subject mentioned
|
|
3054
3156
|
|
|
3055
|
-
${toolData ? `## LIVE DATA FROM TOOLS:\n${toolData}\n` : '## LIVE DATA: No tool data was fetched for this step.\n'}
|
|
3157
|
+
${attachmentText ? `## ATTACHED FILE CONTENT:\n${attachmentText}\n` : ''}${toolData ? `## LIVE DATA FROM TOOLS:\n${toolData}\n` : '## LIVE DATA: No tool data was fetched for this step.\n'}
|
|
3056
3158
|
${context ? `## OUTPUT FROM PREVIOUS AGENTS:\n${context}\n` : ''}`;
|
|
3057
3159
|
userMsg = hasRealData
|
|
3058
3160
|
? `Based ONLY on the real data above, complete this task specifically for the subject in the WORKFLOW GOAL: ${stepPrompt}`
|
package/src/constants.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
|
|
|
5
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
6
|
const __dirname = path.dirname(__filename);
|
|
7
7
|
|
|
8
|
-
export const VERSION = '13.2.
|
|
8
|
+
export const VERSION = '13.2.72';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
package/src/services/web-ui.mjs
CHANGED
|
@@ -3371,6 +3371,18 @@ function downloadStudioPDF() {
|
|
|
3371
3371
|
var task = studioState.task || 'NHA Studio Report';
|
|
3372
3372
|
var today = new Date().toLocaleDateString('it-IT', {day:'2-digit',month:'2-digit',year:'numeric'});
|
|
3373
3373
|
var nodes = studioState.nodes || [];
|
|
3374
|
+
var fileName = (task).slice(0, 60).replace(/[^a-z0-9\s]/gi,'').trim().replace(/\s+/g,'-') || 'NHA-Studio';
|
|
3375
|
+
|
|
3376
|
+
// If canvas exists, download the canvas HTML directly (preserves colors and layout)
|
|
3377
|
+
if (studioState.canvas) {
|
|
3378
|
+
var blob = new Blob([studioState.canvas], {type: 'text/html'});
|
|
3379
|
+
var url = URL.createObjectURL(blob);
|
|
3380
|
+
var a = document.createElement('a');
|
|
3381
|
+
a.href = url; a.target = '_blank'; a.download = fileName + '.html';
|
|
3382
|
+
document.body.appendChild(a); a.click(); document.body.removeChild(a);
|
|
3383
|
+
setTimeout(function(){ URL.revokeObjectURL(url); }, 5000);
|
|
3384
|
+
return;
|
|
3385
|
+
}
|
|
3374
3386
|
|
|
3375
3387
|
// Build sections for each agent
|
|
3376
3388
|
function mdToPdfHtml(raw) {
|
|
@@ -3520,7 +3532,13 @@ async function runStudio() {
|
|
|
3520
3532
|
var taskForPlan = studioState.attachmentContext
|
|
3521
3533
|
? task + nl + nl + '[User has attached a file: ' + studioState.attachmentName + '. Agents will receive the full content.]'
|
|
3522
3534
|
: task;
|
|
3523
|
-
|
|
3535
|
+
// Include PDF info in plan request so server can add DocumentReaderAgent step
|
|
3536
|
+
var planBody = {task: taskForPlan};
|
|
3537
|
+
if (studioState.attachmentContext && studioState.attachmentContext.indexOf('[ATTACHED PDF:') === 0) {
|
|
3538
|
+
planBody.hasPdf = true;
|
|
3539
|
+
planBody.pdfName = studioState.attachmentName || 'document.pdf';
|
|
3540
|
+
}
|
|
3541
|
+
var planRes = await apiPost('/api/studio/plan', planBody);
|
|
3524
3542
|
if (!planRes || !planRes.steps || !planRes.steps.length) {
|
|
3525
3543
|
studioLog('Studio', '⚠', 'Could not plan workflow. Check your LLM provider config.', 'error');
|
|
3526
3544
|
studioState.running = false;
|
|
@@ -3780,11 +3798,31 @@ function runStudioStep(idx, node, task, context, stepDef, signal) {
|
|
|
3780
3798
|
return new Promise(function(resolve) {
|
|
3781
3799
|
var output = '';
|
|
3782
3800
|
var canvasHtml = null;
|
|
3783
|
-
// Inject attachment
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3801
|
+
// Inject attachment into first step only — pass PDF/image as dedicated fields,
|
|
3802
|
+
// NOT as raw base64 in context (would cause 100k+ token overflow for any real PDF).
|
|
3803
|
+
var bodyObj = {stepIdx: idx, agent: node.agent, task: task, context: context, stepDef: stepDef};
|
|
3804
|
+
if (idx === 0 && studioState.attachmentContext) {
|
|
3805
|
+
var ac = studioState.attachmentContext;
|
|
3806
|
+
var isPdfAttach = ac.indexOf('[ATTACHED PDF:') === 0;
|
|
3807
|
+
var isImgAttach = ac.indexOf('[ATTACHED IMAGE:') === 0;
|
|
3808
|
+
// Extract base64 data URL from attachment context
|
|
3809
|
+
var b64Match = ac.indexOf('Base64: ');
|
|
3810
|
+
var dataUrl = b64Match >= 0 ? ac.slice(b64Match + 8).trim() : '';
|
|
3811
|
+
if (isPdfAttach && dataUrl) {
|
|
3812
|
+
// Pass PDF as dedicated field — agent/llm handles it natively
|
|
3813
|
+
bodyObj.pdfBase64 = dataUrl;
|
|
3814
|
+
bodyObj.pdfName = studioState.attachmentName;
|
|
3815
|
+
// Add a short note in context instead of the full base64
|
|
3816
|
+
bodyObj.context = '[User attached PDF: ' + studioState.attachmentName + ']' +
|
|
3817
|
+
(context ? String.fromCharCode(10) + String.fromCharCode(10) + context : '');
|
|
3818
|
+
} else if (isImgAttach && dataUrl) {
|
|
3819
|
+
bodyObj.imageBase64 = dataUrl;
|
|
3820
|
+
bodyObj.imageMimeType = dataUrl.indexOf('data:image/png') === 0 ? 'image/png' : 'image/jpeg';
|
|
3821
|
+
bodyObj.context = '[User attached image: ' + studioState.attachmentName + ']' +
|
|
3822
|
+
(context ? String.fromCharCode(10) + String.fromCharCode(10) + context : '');
|
|
3823
|
+
}
|
|
3824
|
+
}
|
|
3825
|
+
var body = JSON.stringify(bodyObj);
|
|
3788
3826
|
var fetchOpts = {method: 'POST', headers: {'Content-Type': 'application/json'}, body: body};
|
|
3789
3827
|
if (signal) fetchOpts.signal = signal;
|
|
3790
3828
|
|