function authHeaders(extra) { // The app's global getAuthHeaders includes Content-Type: application/json; // without it, Express never parses JSON bodies (empty message, etc.). var base; if (typeof window !== 'undefined' && typeof window.getAuthHeaders === 'function') { base = window.getAuthHeaders(); } else { var token = typeof localStorage === 'undefined' ? null : localStorage.getItem('auth_token'); base = token ? { Authorization: 'Bearer ' + token } : {}; } if (extra) Object.assign(base, extra); return base; } function parseJsonWithStatus(response) { return response.json().catch(function () { return {}; }).then(function(data) { data._status = response.status; return data; }); } export function fetchAssistantStatus() { return fetch('/api/clinical-assistant/status', { headers: authHeaders(), credentials: 'same-origin' }) .then(function(r) { return r.json(); }); } export function fetchAssistantExamples() { return fetch('/api/clinical-assistant/examples', { headers: authHeaders(), credentials: 'same-origin' }) .then(function(r) { return r.json(); }); } export function openAssistantStream(payload, options) { options = options || {}; return fetch('/api/clinical-assistant/chat/stream', { method: 'POST', headers: authHeaders(), credentials: 'same-origin', signal: options.signal, body: JSON.stringify(payload) }); } export function fetchAssistantChat(payload, options) { options = options || {}; return fetch('/api/clinical-assistant/chat', { method: 'POST', headers: authHeaders(), credentials: 'same-origin', signal: options.signal, body: JSON.stringify(payload) }).then(parseJsonWithStatus); } export function requestAssistantImage(prompt) { return fetch('/api/clinical-assistant/image', { method: 'POST', headers: authHeaders(), credentials: 'same-origin', body: JSON.stringify({ prompt: prompt }) }).then(function(r) { return r.json(); }); } export function translateAssistantMessage(message, target, provider, format) { return fetch('/api/clinical-assistant/translate', { method: 'POST', headers: authHeaders(), credentials: 'same-origin', body: JSON.stringify({ message: message, target: target, provider: provider, format: format || 'text' }) }).then(parseJsonWithStatus); } var imageDraft = null; export function startAssistantImageJob(prompt, history, imageModel) { const input = { prompt, ...(history === undefined ? {} : { history }), ...(imageModel ? { imageModel } : {}) }; const identity = JSON.stringify(input); if (!imageDraft || imageDraft.identity !== identity) imageDraft = { identity, body: { ...input, idempotencyKey: crypto.randomUUID() } }; return fetch('/api/clinical-assistant/image/jobs', { method: 'POST', headers: authHeaders(), credentials: 'same-origin', body: JSON.stringify(imageDraft.body) }).then(function(r) { return r.json(); }); } export function fetchAssistantImageJob(jobId) { return fetch('/api/clinical-assistant/image/jobs/' + encodeURIComponent(jobId), { headers: authHeaders(), credentials: 'same-origin' }).then(function(r) { return r.json(); }); } export function saveAssistantChat(payload) { return fetch('/api/clinical-assistant/chats', { method: 'POST', headers: authHeaders(), credentials: 'same-origin', body: JSON.stringify(payload) }).then(parseJsonWithStatus); } export function fetchSavedAssistantChats() { return fetch('/api/clinical-assistant/chats', { headers: authHeaders(), credentials: 'same-origin' }) .then(function(r) { return r.json(); }); } export function fetchSavedAssistantChat(id) { return fetch('/api/clinical-assistant/chats/' + encodeURIComponent(id), { headers: authHeaders(), credentials: 'same-origin' }) .then(parseJsonWithStatus); } export function deleteSavedAssistantChat(id) { return fetch('/api/clinical-assistant/chats/' + encodeURIComponent(id), { method: 'DELETE', headers: authHeaders(), credentials: 'same-origin' }).then(function(r) { return r.json(); }); } // Image attachments ride the outgoing clinical question payload for inference. // Once a question is sent, its attachments persist with the saved chat. The // server enforces the same limits authoritatively; these mirror them for early // feedback. export var assistantAttachmentLimits = Object.freeze({ mimeTypes: ['image/png', 'image/jpeg', 'image/webp'], maxImages: 4, maxImageBytes: 5 * 1024 * 1024, maxTotalBytes: 10 * 1024 * 1024 }); export function assistantAttachmentPayload(attachments) { return (Array.isArray(attachments) ? attachments : []).map(function(attachment) { return { dataBase64: attachment.dataBase64, mimeType: attachment.mimeType }; }); } export function fetchAssistantImageJobs() { return fetch('/api/clinical-assistant/image/jobs?limit=24', { headers: authHeaders(), credentials: 'same-origin' }) .then(parseJsonWithStatus); } export function renameSavedAssistantChat(id, title) { return fetch('/api/clinical-assistant/chats/' + encodeURIComponent(id), { method: 'PATCH', headers: authHeaders(Object.assign({ 'Content-Type': 'application/json' })), credentials: 'same-origin', body: JSON.stringify({ title: title }) }).then(parseJsonWithStatus); }