Skip to content

Commit a53d679

Browse files
committed
garotm - interfact changes and print fixes.
1 parent 9d6ab25 commit a53d679

4 files changed

Lines changed: 118 additions & 36 deletions

File tree

frontend/src/App.tsx

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const OLLAMA_DEFAULT_MODEL = "llama3.2";
55
import { invoke } from "@tauri-apps/api/core";
66
import { open } from "@tauri-apps/plugin-dialog";
77
import { api, type DocumentDetail, type DocumentRow, type Health, type Settings } from "./api";
8+
import { printClinicalReport } from "./printReport";
89
import { isTauriRuntime } from "./tauri-env";
910

1011
type Tab = "dashboard" | "documents" | "settings";
@@ -166,7 +167,7 @@ export default function App() {
166167
<Stat
167168
title="LLM endpoint"
168169
value={settings?.llm_base_url ? "Set" : "Default"}
169-
hint={settings?.llm_base_url ?? "http://127.0.0.1:8080"}
170+
hint={settings?.llm_base_url ?? "http://127.0.0.1:11434/v1"}
170171
good
171172
/>
172173
</div>
@@ -244,7 +245,7 @@ export default function App() {
244245
)}
245246
</main>
246247

247-
<footer className="border-t border-slate-800/80 py-4 text-center text-xs text-slate-600 print:hidden">
248+
<footer className="border-t border-slate-800/80 py-4 text-center text-xs text-slate-400 print:hidden">
248249
Local-only processing · No cloud · HIPAA-aligned deployment on your network
249250
</footer>
250251
</div>
@@ -359,47 +360,52 @@ function SettingsForm(props: {
359360
function ReportCard(props: { detail: DocumentDetail; onClose: () => void }) {
360361
const d = props.detail;
361362
return (
362-
<article className="rounded-xl border border-slate-800 bg-slate-900/40 overflow-hidden print:border-slate-300 print:bg-white print:shadow-none">
363-
<div className="flex items-start justify-between gap-4 px-6 py-4 border-b border-slate-800 print:border-slate-200">
363+
<article className="rounded-xl border border-slate-700 bg-slate-950/50 overflow-hidden shadow-lg">
364+
<div className="flex items-start justify-between gap-4 px-5 py-4 border-b border-slate-700 bg-slate-900/80">
364365
<div>
365-
<h3 className="text-base font-semibold text-white print:text-slate-900">{d.file_name}</h3>
366-
<p className="text-xs text-slate-500 mt-1 print:text-slate-600">
366+
<h3 className="text-base font-semibold text-slate-100">{d.file_name}</h3>
367+
<p className="text-xs text-slate-400 mt-1">
367368
{d.source_type} · {new Date(d.created_at).toLocaleString()}
368369
{d.confidence != null && ` · Confidence ${Math.round(d.confidence * 100)}%`}
369370
</p>
370371
</div>
371-
<button
372-
type="button"
373-
onClick={() => window.print()}
374-
className="text-xs rounded-md border border-slate-600 px-3 py-1.5 text-slate-300 hover:bg-slate-800 print:hidden"
375-
>
376-
Print report
377-
</button>
378-
<button
379-
type="button"
380-
onClick={props.onClose}
381-
className="text-xs text-slate-500 hover:text-white print:hidden"
382-
>
383-
Close
384-
</button>
372+
<div className="flex items-center gap-2 shrink-0">
373+
<button
374+
type="button"
375+
onClick={() => printClinicalReport(d)}
376+
className="text-xs rounded-md border border-clinical-teal/50 bg-clinical-teal/15 px-3 py-1.5 text-clinical-teal hover:bg-clinical-teal/25"
377+
>
378+
Print report
379+
</button>
380+
<button
381+
type="button"
382+
onClick={props.onClose}
383+
className="text-xs text-slate-400 hover:text-white px-2"
384+
>
385+
Close
386+
</button>
387+
</div>
385388
</div>
386-
<div className="px-6 py-6 space-y-6 print-sheet print:block">
389+
<div className="bg-slate-50 text-slate-900 px-5 py-5 space-y-6 border-t border-slate-200/80">
387390
<section>
388-
<h4 className="text-xs font-semibold uppercase tracking-wide text-slate-500 print:text-slate-600">
389-
Clinical synthesis
390-
</h4>
391-
<div className="mt-2 prose prose-invert max-w-none text-sm leading-relaxed text-slate-200 print:prose-slate print:text-slate-800 whitespace-pre-wrap">
391+
<h4 className="text-xs font-semibold uppercase tracking-wide text-slate-600">Clinical synthesis</h4>
392+
<p className="mt-1 text-xs text-slate-500 leading-snug">
393+
Text extracted from your file, then summarized by your local LLM (e.g. Ollama). Verify against the
394+
original record before clinical use.
395+
</p>
396+
<div className="mt-3 text-sm leading-relaxed text-slate-800 whitespace-pre-wrap">
392397
{d.summary_text ?? "No summary available."}
393398
</div>
394399
{d.error_message && (
395-
<p className="mt-3 text-sm text-rose-300 print:text-rose-700">{d.error_message}</p>
400+
<p className="mt-3 text-sm text-rose-700 font-medium bg-rose-50 border border-rose-200 rounded-md px-3 py-2">
401+
{d.error_message}
402+
</p>
396403
)}
397404
</section>
398405
<section>
399-
<h4 className="text-xs font-semibold uppercase tracking-wide text-slate-500 print:text-slate-600">
400-
Extracted context (preview)
401-
</h4>
402-
<pre className="mt-2 text-xs bg-slate-950/80 border border-slate-800 rounded-lg p-3 overflow-auto max-h-48 text-slate-400 print:text-slate-700 print:bg-slate-50 print:border-slate-200">
406+
<h4 className="text-xs font-semibold uppercase tracking-wide text-slate-600">Extracted context (preview)</h4>
407+
<p className="mt-1 text-xs text-slate-500">Raw text or structured fields passed to the model (truncated).</p>
408+
<pre className="mt-2 text-xs bg-white border border-slate-200 rounded-lg p-3 overflow-auto max-h-64 text-slate-700 shadow-inner">
403409
{(d.raw_preview ?? "").slice(0, 8000)}
404410
</pre>
405411
</section>

frontend/src/printReport.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import type { DocumentDetail } from "./api";
2+
3+
function escapeHtml(s: string): string {
4+
return s
5+
.replace(/&/g, "&amp;")
6+
.replace(/</g, "&lt;")
7+
.replace(/>/g, "&gt;")
8+
.replace(/"/g, "&quot;");
9+
}
10+
11+
/**
12+
* Opens the system print dialog with a readable, high-contrast document.
13+
* Uses a hidden iframe so printing works reliably in Tauri/WebKit (main window `print()` is often a no-op).
14+
*/
15+
export function printClinicalReport(d: DocumentDetail): void {
16+
const title = escapeHtml(d.file_name);
17+
const meta = escapeHtml(
18+
`${d.source_type} · ${new Date(d.created_at).toLocaleString()}${
19+
d.confidence != null ? ` · Confidence ${Math.round(d.confidence * 100)}%` : ""
20+
}`,
21+
);
22+
const summary = escapeHtml(d.summary_text ?? "No summary available.");
23+
const err = d.error_message ? `<p class="err">${escapeHtml(d.error_message)}</p>` : "";
24+
const raw = escapeHtml((d.raw_preview ?? "").slice(0, 16000));
25+
26+
const html = `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"/><title>${title}</title>
27+
<style>
28+
body { font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif; padding: 24px; color: #0f172a; max-width: 52rem; margin: 0 auto; line-height: 1.5; }
29+
h1 { font-size: 1.25rem; font-weight: 600; margin: 0 0 8px; color: #020617; }
30+
.meta { color: #475569; font-size: 0.8125rem; margin-bottom: 20px; }
31+
h2 { font-size: 0.6875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: #64748b; margin: 20px 0 8px; }
32+
.summary { white-space: pre-wrap; font-size: 0.9375rem; color: #1e293b; }
33+
pre { background: #f1f5f9; border: 1px solid #e2e8f0; padding: 12px; border-radius: 8px; font-size: 0.75rem; color: #334155; white-space: pre-wrap; word-break: break-word; max-height: 50vh; overflow: auto; }
34+
.err { color: #b91c1c; font-size: 0.875rem; margin-top: 12px; }
35+
@media print { body { padding: 12px; } pre { max-height: none; } }
36+
</style></head><body>
37+
<h1>${title}</h1>
38+
<div class="meta">${meta}</div>
39+
<h2>Clinical synthesis</h2>
40+
<div class="summary">${summary}</div>
41+
${err}
42+
<h2>Extracted context (preview)</h2>
43+
<pre>${raw}</pre>
44+
</body></html>`;
45+
46+
const iframe = document.createElement("iframe");
47+
iframe.setAttribute("aria-hidden", "true");
48+
Object.assign(iframe.style, {
49+
position: "fixed",
50+
right: "0",
51+
bottom: "0",
52+
width: "0",
53+
height: "0",
54+
border: "0",
55+
opacity: "0",
56+
});
57+
document.body.appendChild(iframe);
58+
59+
const doc = iframe.contentDocument;
60+
const win = iframe.contentWindow;
61+
if (!doc || !win) {
62+
document.body.removeChild(iframe);
63+
window.print();
64+
return;
65+
}
66+
67+
doc.open();
68+
doc.write(html);
69+
doc.close();
70+
71+
setTimeout(() => {
72+
try {
73+
win.focus();
74+
win.print();
75+
} finally {
76+
setTimeout(() => {
77+
iframe.remove();
78+
}, 400);
79+
}
80+
}, 0);
81+
}

frontend/src/styles.css

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,3 @@
1111
}
1212
}
1313

14-
@layer utilities {
15-
.print-sheet {
16-
@apply max-w-3xl mx-auto bg-white text-slate-900 p-10 shadow-none;
17-
}
18-
}

frontend/tsconfig.tsbuildinfo

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"root":["./src/app.tsx","./src/api.ts","./src/main.tsx","./src/vite-env.d.ts"],"version":"5.9.3"}
1+
{"root":["./src/app.tsx","./src/api.test.ts","./src/api.ts","./src/main.tsx","./src/printreport.ts","./src/tauri-env.test.ts","./src/tauri-env.ts","./src/vite-env.d.ts"],"version":"5.9.3"}

0 commit comments

Comments
 (0)