148 lines
5.2 KiB
JavaScript
148 lines
5.2 KiB
JavaScript
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);
|
|
}
|