From 6cfad65639b59e956c8f44ca629c5866c4c22e4e Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 8 May 2026 00:58:44 +0200 Subject: [PATCH] extract assistant frontend api client --- public/js/assistant/api.js | 74 ++++++++++++++++++++++++++++++++++ public/js/clinicalAssistant.js | 69 ++++++++++++------------------- 2 files changed, 100 insertions(+), 43 deletions(-) create mode 100644 public/js/assistant/api.js diff --git a/public/js/assistant/api.js b/public/js/assistant/api.js new file mode 100644 index 0000000..8769fc6 --- /dev/null +++ b/public/js/assistant/api.js @@ -0,0 +1,74 @@ +function authHeaders() { + return window.getAuthHeaders ? window.getAuthHeaders() : { 'Content-Type': 'application/json' }; +} + +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) { + return fetch('/api/clinical-assistant/chat/stream', { + method: 'POST', + headers: authHeaders(), + credentials: 'same-origin', + body: JSON.stringify(payload) + }); +} + +export function fetchAssistantChat(payload) { + return fetch('/api/clinical-assistant/chat', { + method: 'POST', + headers: authHeaders(), + credentials: 'same-origin', + 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 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(); }); +} diff --git a/public/js/clinicalAssistant.js b/public/js/clinicalAssistant.js index f111913..be5225b 100644 --- a/public/js/clinicalAssistant.js +++ b/public/js/clinicalAssistant.js @@ -5,6 +5,17 @@ // ============================================================ import { EMPTY_PROMPT_SETS } from './assistant/data.js'; import { escapeAttr, escapeHtml, renderAssistantMarkdown } from './assistant/citations.js'; +import { + deleteSavedAssistantChat, + fetchAssistantChat, + fetchAssistantExamples, + fetchAssistantStatus, + fetchSavedAssistantChat, + fetchSavedAssistantChats, + openAssistantStream, + requestAssistantImage, + saveAssistantChat +} from './assistant/api.js'; (function () { 'use strict'; @@ -68,8 +79,7 @@ import { escapeAttr, escapeHtml, renderAssistantMarkdown } from './assistant/cit } function loadStatus() { - fetch('/api/clinical-assistant/status', { headers: getAuthHeaders(), credentials: 'same-origin' }) - .then(function (r) { return r.json(); }) + fetchAssistantStatus() .then(function (data) { var label = document.getElementById('assistant-model-label'); if (label && data.success) { @@ -82,8 +92,7 @@ import { escapeAttr, escapeHtml, renderAssistantMarkdown } from './assistant/cit } function loadExamples() { - fetch('/api/clinical-assistant/examples', { headers: getAuthHeaders(), credentials: 'same-origin' }) - .then(function (r) { return r.json(); }) + fetchAssistantExamples() .then(function (data) { if (!data.success || !Array.isArray(data.examples) || !data.examples.length) return; dynamicExamples = data.examples; @@ -150,12 +159,7 @@ import { escapeAttr, escapeHtml, renderAssistantMarkdown } from './assistant/cit } async function streamAssistantResponse(payload, loading) { - var response = await fetch('/api/clinical-assistant/chat/stream', { - method: 'POST', - headers: getAuthHeaders(), - credentials: 'same-origin', - body: JSON.stringify(payload) - }); + var response = await openAssistantStream(payload); if (!response.ok || !response.body) { var fallback = await response.json().catch(function () { return {}; }); throw new Error(fallback.error || ('Request failed (' + response.status + ')')); @@ -250,14 +254,8 @@ import { escapeAttr, escapeHtml, renderAssistantMarkdown } from './assistant/cit } async function fetchAssistantFallback(payload) { - var response = await fetch('/api/clinical-assistant/chat', { - method: 'POST', - headers: getAuthHeaders(), - credentials: 'same-origin', - body: JSON.stringify(payload) - }); - var data = await response.json().catch(function () { return {}; }); - if (!response.ok || !data.success) throw new Error(data.error || ('Request failed (' + response.status + ')')); + var data = await fetchAssistantChat(payload); + if (!data.success) throw new Error(data.error || ('Request failed (' + data._status + ')')); return data; } @@ -471,11 +469,7 @@ import { escapeAttr, escapeHtml, renderAssistantMarkdown } from './assistant/cit if (fromChat) setBusy(true, 'Generating image...'); var loading = fromChat ? appendLoadingMessage('Generating image', 'Creating the requested clinical visual...') : null; if (out) out.innerHTML = '

Generating image...

'; - fetch('/api/clinical-assistant/image', { - method: 'POST', headers: getAuthHeaders(), credentials: 'same-origin', - body: JSON.stringify({ prompt: prompt }) - }) - .then(function (r) { return r.json(); }) + requestAssistantImage(prompt) .then(function (data) { if (!data.success) throw new Error(data.error || 'Image generation failed'); var src = data.imageUrl || data.url || (data.base64 ? ('data:image/png;base64,' + data.base64) : ''); @@ -762,19 +756,13 @@ import { escapeAttr, escapeHtml, renderAssistantMarkdown } from './assistant/cit var titleEl = document.getElementById('assistant-save-title'); var title = String(titleEl && titleEl.value || deriveChatTitle()).trim() || deriveChatTitle(); setBusy(true, 'Saving chat...'); - fetch('/api/clinical-assistant/chats', { - method: 'POST', - headers: getAuthHeaders(), - credentials: 'same-origin', - body: JSON.stringify({ - title: title, - messages: messages, - sources: lastSources, - lastAnswer: lastAnswer, - generatedImage: imageForSavedChatPayload(lastGeneratedImageSrc) - }) + saveAssistantChat({ + title: title, + messages: messages, + sources: lastSources, + lastAnswer: lastAnswer, + generatedImage: imageForSavedChatPayload(lastGeneratedImageSrc) }) - .then(function (r) { return r.json().then(function (data) { data._status = r.status; return data; }); }) .then(function (data) { setBusy(false, 'Ready'); if (!data.success) throw new Error(data.error || 'Save failed'); @@ -805,8 +793,7 @@ import { escapeAttr, escapeHtml, renderAssistantMarkdown } from './assistant/cit } function loadSavedChats() { - fetch('/api/clinical-assistant/chats', { headers: getAuthHeaders(), credentials: 'same-origin' }) - .then(function (r) { return r.json(); }) + fetchSavedAssistantChats() .then(function (data) { if (data.success) renderSavedChats(data.chats || []); }) .catch(function () {}); } @@ -830,8 +817,7 @@ import { escapeAttr, escapeHtml, renderAssistantMarkdown } from './assistant/cit } function loadSavedChat(id) { - fetch('/api/clinical-assistant/chats/' + encodeURIComponent(id), { headers: getAuthHeaders(), credentials: 'same-origin' }) - .then(function (r) { return r.json().then(function (data) { data._status = r.status; return data; }); }) + fetchSavedAssistantChat(id) .then(function (data) { if (!data.success) throw new Error(data.error || 'Load failed'); restoreSavedChat(data.chat && data.chat.payload || {}); @@ -853,10 +839,7 @@ import { escapeAttr, escapeHtml, renderAssistantMarkdown } from './assistant/cit }, 5000); return; } - fetch('/api/clinical-assistant/chats/' + encodeURIComponent(id), { - method: 'DELETE', headers: getAuthHeaders(), credentials: 'same-origin' - }) - .then(function (r) { return r.json(); }) + deleteSavedAssistantChat(id) .then(function () { loadSavedChats(); if (typeof showToast === 'function') showToast('Deleted saved chat', 'success'); }) .catch(function () { if (typeof showToast === 'function') showToast('Delete failed', 'error'); }); }