diff --git a/src/routes/clinicalAssistant.js b/src/routes/clinicalAssistant.js index dc2fb09..3c6351c 100644 --- a/src/routes/clinicalAssistant.js +++ b/src/routes/clinicalAssistant.js @@ -243,7 +243,7 @@ router.post('/clinical-assistant/chat', async function(req, res) { var answer = stripModelSourcesSection(String(ai.content || '').trim()); logger.audit(req.user.id, 'clinical_assistant_query', 'Clinical assistant query', req, { - category: 'clinical', model: ai.model || chatModel, duration: Date.now() - started + category: 'clinical', model: ai.model || prepared.chatModel, duration: Date.now() - started }); res.json({ @@ -449,44 +449,6 @@ async function prepareAssistantChat(body) { }; } -router.post('/clinical-assistant/export-summary', async function(req, res) { - try { - if (Array.isArray(req.body.items) && req.body.items.length) { - var exportItems = await summarizeExportItems(req.body.items); - return res.json({ success: true, items: exportItems }); - } - - var answer = String(req.body.answer || '').trim(); - var sources = Array.isArray(req.body.sources) ? req.body.sources.slice(0, 20) : []; - if (!answer) return res.status(400).json({ error: 'Answer is required' }); - - var chatModel = await getSetting('clinical_assistant.chat_model', '') || await getSetting('models.default', ''); - var sourceList = sources.map(function(s, idx) { - var n = s.number || idx + 1; - return '[' + n + '] ' + cleanTitle(s.title || s.resource || 'Source') + (s.page ? ', page ' + s.page : ''); - }).join('\n'); - var ai = await callAI([ - { - role: 'system', - content: 'Create an abridged clinical export summary. Preserve factual meaning. Use only citation numbers already present in the original answer and provided source list. Do not invent, renumber, merge, or move citations. If a claim cannot keep its citation, omit the claim. No references section.' - }, - { - role: 'user', - content: 'Original answer:\n' + answer.substring(0, 8000) + '\n\nAvailable cited sources:\n' + sourceList + '\n\nReturn a concise export version with the same citation numbers where supported.' - } - ], { - model: chatModel || undefined, - temperature: 0.05, - maxTokens: 900 - }); - - var summary = stripModelSourcesSection(String(ai.content || '').trim()); - res.json({ success: true, summary: summary || answer, model: ai.model || chatModel || null }); - } catch (e) { - res.status(500).json({ error: assistantErrorMessage(e) }); - } -}); - async function rewriteSearchQuery(message, history, chatModel) { message = String(message || '').trim(); history = Array.isArray(history) ? history.filter(function(m) { return m && (m.role === 'user' || m.role === 'assistant') && m.content; }).slice(-8) : []; @@ -557,63 +519,6 @@ function previousAssistantTopic(history) { return ''; } -async function summarizeExportItems(items) { - var chatModel = await getSetting('clinical_assistant.chat_model', '') || await getSetting('models.default', ''); - var clean = items.slice(0, 20).map(function(item, idx) { - return { - question: clip(item.question || ('Question ' + (idx + 1)), 1000), - answer: clip(item.answer || '', 12000), - sources: Array.isArray(item.sources) ? item.sources.slice(0, 25) : [] - }; - }).filter(function(item) { return item.answer; }); - - var out = []; - for (var i = 0; i < clean.length; i++) { - out.push(await summarizeExportItem(clean[i], i, chatModel)); - } - return out; -} - -async function summarizeExportItem(item, idx, chatModel) { - var sourceList = item.sources.map(function(s, sidx) { - var n = s.number || sidx + 1; - return '[' + n + '] ' + cleanTitle(s.title || s.resource || 'Source') + (s.page ? ', page ' + s.page : ''); - }).join('\n'); - - try { - var ai = await callAI([ - { - role: 'system', - content: 'Create a clinical export section heading and abridged summary. Return strict JSON only: {"heading":"...","summary":"..."}. The heading must be specific and clinically meaningful, not a vague follow-up like "What dose?". Preserve factual meaning. Use only citation numbers already present in the answer and provided source list. Do not invent, renumber, merge, or move citations. No references section.' - }, - { - role: 'user', - content: 'User question:\n' + item.question + '\n\nAnswer:\n' + item.answer + '\n\nAvailable cited sources:\n' + sourceList - } - ], { - model: chatModel || undefined, - temperature: 0.05, - maxTokens: 700 - }); - var parsed = parseJsonObject(String(ai.content || '')); - return { - question: item.question, - heading: cleanExportHeading(parsed.heading || item.question, idx), - summary: stripModelSourcesSection(String(parsed.summary || '').trim()) || item.answer, - answer: item.answer, - sources: item.sources - }; - } catch (e) { - return { - question: item.question, - heading: cleanExportHeading(item.question, idx), - summary: item.answer, - answer: item.answer, - sources: item.sources - }; - } -} - async function semanticSearch(query, opts) { return callMcpTool('nc_semantic_search', { query: query, @@ -1136,23 +1041,6 @@ function cleanSavedChatTitle(title) { return clip(title, MAX_SAVED_CHAT_TITLE).replace(/[\r\n\t]+/g, ' ').replace(/\s+/g, ' ').trim() || 'Clinical assistant chat'; } -function parseJsonObject(text) { - text = String(text || '').trim().replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/i, ''); - try { return JSON.parse(text); } catch (e) {} - var start = text.indexOf('{'); - var end = text.lastIndexOf('}'); - if (start !== -1 && end > start) { - try { return JSON.parse(text.slice(start, end + 1)); } catch (e2) {} - } - return {}; -} - -function cleanExportHeading(heading, idx) { - heading = clip(heading, 120).replace(/^#+\s*/, '').replace(/[\r\n\t]+/g, ' ').replace(/\s+/g, ' ').trim(); - if (!heading || /^(what|which|when|why|how|and|also|dose\??|what dose\??)$/i.test(heading)) return 'Clinical Question ' + (idx + 1); - return heading; -} - async function getAvailableExamples() { return promptPool.getAvailableExamples(); }