@omniradiology/omnirad 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/README.md +438 -0
  2. package/app/api/ai-config/route.ts +131 -0
  3. package/app/api/ai-config/test/route.ts +49 -0
  4. package/app/api/auth/auto-login/route.ts +66 -0
  5. package/app/api/auth/check/route.ts +17 -0
  6. package/app/api/auth/login/route.ts +72 -0
  7. package/app/api/auth/logout/route.ts +25 -0
  8. package/app/api/auth/me/route.ts +75 -0
  9. package/app/api/auth/password/route.ts +49 -0
  10. package/app/api/auth/setup/route.ts +63 -0
  11. package/app/api/auth/users/route.ts +100 -0
  12. package/app/api/auth/wipe/route.ts +27 -0
  13. package/app/api/compliance/anonymize/patient/[id]/route.ts +104 -0
  14. package/app/api/compliance/audit/route.ts +110 -0
  15. package/app/api/compliance/export/patient/[id]/route.ts +108 -0
  16. package/app/api/compliance/restrict/patient/[id]/route.ts +59 -0
  17. package/app/api/compliance/settings/route.ts +93 -0
  18. package/app/api/copilot/annotate/route.ts +94 -0
  19. package/app/api/copilot/chat/route.ts +238 -0
  20. package/app/api/copilot/history/route.ts +95 -0
  21. package/app/api/copilot/reports/route.ts +81 -0
  22. package/app/api/fhir/Bundle/report/[id]/route.ts +85 -0
  23. package/app/api/fhir/DiagnosticReport/[id]/route.ts +45 -0
  24. package/app/api/fhir/ImagingStudy/[id]/route.ts +57 -0
  25. package/app/api/fhir/Patient/[id]/route.ts +26 -0
  26. package/app/api/fhir/ServiceRequest/route.ts +85 -0
  27. package/app/api/fhir/config/route.ts +102 -0
  28. package/app/api/fhir/config/test-connection/route.ts +49 -0
  29. package/app/api/fhir/metadata/route.ts +51 -0
  30. package/app/api/pacs/metadata/route.ts +32 -0
  31. package/app/api/pacs/qido/instances/route.ts +39 -0
  32. package/app/api/pacs/qido/series/route.ts +38 -0
  33. package/app/api/pacs/qido/studies/route.ts +37 -0
  34. package/app/api/pacs/test/route.ts +30 -0
  35. package/app/api/pacs/wado/render/route.ts +51 -0
  36. package/app/api/patients/[id]/reports/route.ts +18 -0
  37. package/app/api/patients/[id]/route.ts +43 -0
  38. package/app/api/patients/merge/route.ts +57 -0
  39. package/app/api/patients/route.ts +67 -0
  40. package/app/api/patients/search/route.ts +25 -0
  41. package/app/api/reports/[id]/route.ts +84 -0
  42. package/app/api/reports/[id]/status/route.ts +87 -0
  43. package/app/api/reports/clear/route.ts +16 -0
  44. package/app/api/reports/route.ts +112 -0
  45. package/app/api/segmentation-config/route.ts +238 -0
  46. package/app/api/settings/route.ts +245 -0
  47. package/app/api/settings/test-supabase/route.ts +103 -0
  48. package/app/api/upload/route.ts +48 -0
  49. package/app/copilot/page.tsx +30 -0
  50. package/app/globals.css +141 -0
  51. package/app/history/page.tsx +242 -0
  52. package/app/icon.svg +3 -0
  53. package/app/layout.tsx +47 -0
  54. package/app/login/page.tsx +175 -0
  55. package/app/pacs/page.tsx +78 -0
  56. package/app/page.tsx +125 -0
  57. package/app/patients/[id]/page.tsx +315 -0
  58. package/app/patients/page.tsx +110 -0
  59. package/app/profile/page.tsx +208 -0
  60. package/app/reports/page.tsx +432 -0
  61. package/app/settings/page.tsx +454 -0
  62. package/app/setup/page.tsx +199 -0
  63. package/components/admin/AuditLogTable.tsx +293 -0
  64. package/components/copilot/ActivityIndicator.tsx +215 -0
  65. package/components/copilot/ChatHistoryPanel.tsx +140 -0
  66. package/components/copilot/ChatMessage.tsx +251 -0
  67. package/components/copilot/ClickableReference.tsx +40 -0
  68. package/components/copilot/CopilotCornerstoneViewer.tsx +562 -0
  69. package/components/copilot/CopilotPanel.tsx +311 -0
  70. package/components/copilot/FindingsList.tsx +75 -0
  71. package/components/copilot/ViewerPanel.tsx +460 -0
  72. package/components/copilot/WorkspaceLayout.tsx +398 -0
  73. package/components/dashboard/AIConfigPanel.tsx +339 -0
  74. package/components/dashboard/AppearancePanel.tsx +491 -0
  75. package/components/dashboard/ApprovalModal.tsx +163 -0
  76. package/components/dashboard/CollaborationPanel.tsx +134 -0
  77. package/components/dashboard/CopilotConfigPanel.tsx +337 -0
  78. package/components/dashboard/DicomViewer.tsx +645 -0
  79. package/components/dashboard/FhirIntegrationPanel.tsx +331 -0
  80. package/components/dashboard/FullReportOverlay.tsx +269 -0
  81. package/components/dashboard/ImageViewer.tsx +541 -0
  82. package/components/dashboard/PatientForm.tsx +597 -0
  83. package/components/dashboard/RejectionModal.tsx +74 -0
  84. package/components/dashboard/ReportEditor.tsx +160 -0
  85. package/components/dashboard/ReportTemplates.tsx +729 -0
  86. package/components/dashboard/ReportView.tsx +539 -0
  87. package/components/dashboard/SegmentationConfigPanel.tsx +490 -0
  88. package/components/dashboard/StudyPlaceholder.tsx +17 -0
  89. package/components/dashboard/SupabaseIntegrationPanel.tsx +345 -0
  90. package/components/dashboard/UserManagementPanel.tsx +272 -0
  91. package/components/layout/ClientLayout.tsx +39 -0
  92. package/components/layout/Header.tsx +20 -0
  93. package/components/layout/Sidebar.tsx +119 -0
  94. package/components/pacs/PacsImageViewerModal.tsx +121 -0
  95. package/components/pacs/PacsSearchFilters.tsx +117 -0
  96. package/components/pacs/PacsSeriesViewer.tsx +190 -0
  97. package/components/pacs/PacsStudyTable.tsx +113 -0
  98. package/components/patients/patient-card.tsx +117 -0
  99. package/components/patients/patient-header.tsx +122 -0
  100. package/components/patients/patient-search.tsx +137 -0
  101. package/components/patients/patient-timeline.tsx +153 -0
  102. package/components/settings/ComplianceSettingsPanel.tsx +278 -0
  103. package/components/settings/SecurityPanel.tsx +418 -0
  104. package/components/ui/badge.tsx +19 -0
  105. package/components/ui/basic.tsx +156 -0
  106. package/db/index.ts +350 -0
  107. package/db/migrations/0000_odd_quasimodo.sql +117 -0
  108. package/db/migrations/meta/0000_snapshot.json +778 -0
  109. package/db/migrations/meta/_journal.json +13 -0
  110. package/db/schema.ts +239 -0
  111. package/drizzle.config.ts +10 -0
  112. package/lib/api.ts +689 -0
  113. package/lib/auth.ts +22 -0
  114. package/lib/copilot/action-executor.ts +94 -0
  115. package/lib/copilot/action-types.ts +72 -0
  116. package/lib/copilot/coordinate-mapper.ts +84 -0
  117. package/lib/dicomImageExtractor.ts +103 -0
  118. package/lib/dicomMetadataParser.ts +111 -0
  119. package/lib/fhir/client.ts +25 -0
  120. package/lib/fhir/constants.ts +21 -0
  121. package/lib/fhir/diagnostic-report.ts +88 -0
  122. package/lib/fhir/helpers.ts +73 -0
  123. package/lib/fhir/imaging-study.ts +49 -0
  124. package/lib/fhir/patient.ts +55 -0
  125. package/lib/fhir/service-request.ts +85 -0
  126. package/lib/fhir.ts +6 -0
  127. package/lib/pacs/dicom-utils.ts +72 -0
  128. package/lib/pacs/dicomweb.ts +72 -0
  129. package/lib/pacs/server-utils.ts +37 -0
  130. package/lib/patients.ts +25 -0
  131. package/lib/pdfHelper.ts +119 -0
  132. package/lib/reportHtmlGenerator.ts +581 -0
  133. package/lib/security/audit.ts +180 -0
  134. package/lib/security/authz.ts +246 -0
  135. package/lib/security/phi-redaction.ts +156 -0
  136. package/lib/security/rate-limit.ts +106 -0
  137. package/lib/security/secrets.ts +179 -0
  138. package/lib/supabase.ts +72 -0
  139. package/lib/utils.ts +6 -0
  140. package/next.config.ts +35 -0
  141. package/package.json +76 -0
  142. package/public/file.svg +1 -0
  143. package/public/globe.svg +1 -0
  144. package/public/logo.svg +8 -0
  145. package/public/next.svg +1 -0
  146. package/public/omnirad-favicon.svg +8 -0
  147. package/public/vercel.svg +1 -0
  148. package/public/window.svg +1 -0
  149. package/tsconfig.json +34 -0
  150. package/types/copilot-viewer.ts +155 -0
  151. package/types/copilot.ts +105 -0
  152. package/types/fhir.ts +21 -0
  153. package/types/html2pdf.d.ts +20 -0
  154. package/types/index.ts +139 -0
  155. package/types/pacs.ts +41 -0
@@ -0,0 +1,74 @@
1
+ import * as React from "react";
2
+ import { Button } from "@/components/ui/basic";
3
+ import { X, AlertTriangle } from "lucide-react";
4
+
5
+ interface RejectionModalProps {
6
+ isOpen: boolean;
7
+ onClose: () => void;
8
+ onConfirm: (reason: string, comment: string) => void;
9
+ }
10
+
11
+ export function RejectionModal({ isOpen, onClose, onConfirm }: RejectionModalProps) {
12
+ const [reason, setReason] = React.useState("");
13
+ const [comment, setComment] = React.useState("");
14
+
15
+ if (!isOpen) return null;
16
+
17
+ return (
18
+ <div className="fixed inset-0 z-[60] flex items-center justify-center bg-black/50 backdrop-blur-sm animate-in fade-in zoom-in duration-200">
19
+ <div className="bg-bg-surface w-[450px] rounded-lg shadow-xl border border-border-primary overflow-hidden">
20
+ <div className="flex items-center justify-between p-4 border-b border-border-primary bg-red-50">
21
+ <div className="flex items-center gap-2 text-red-700">
22
+ <AlertTriangle size={20} />
23
+ <h2 className="text-lg font-bold">Reject Report</h2>
24
+ </div>
25
+ <button onClick={onClose} className="text-text-muted hover:text-text-primary">
26
+ <X size={20} />
27
+ </button>
28
+ </div>
29
+
30
+ <div className="p-6 space-y-4">
31
+ <div>
32
+ <label className="block text-sm font-medium text-text-primary mb-1">
33
+ Main Reason for Rejection <span className="text-red-500">*</span>
34
+ </label>
35
+ <input
36
+ type="text"
37
+ placeholder="e.g., Incorrect Findings"
38
+ className="w-full px-3 py-2 border border-border-primary rounded-md focus:outline-none focus:ring-2 focus:ring-red-500/20"
39
+ value={reason}
40
+ onChange={(e) => setReason(e.target.value)}
41
+ />
42
+ </div>
43
+
44
+ <div>
45
+ <label className="block text-sm font-medium text-text-primary mb-1">
46
+ Additional Comments
47
+ </label>
48
+ <textarea
49
+ rows={3}
50
+ placeholder="Provide more details..."
51
+ className="w-full px-3 py-2 border border-border-primary rounded-md focus:outline-none focus:ring-2 focus:ring-red-500/20"
52
+ value={comment}
53
+ onChange={(e) => setComment(e.target.value)}
54
+ />
55
+ </div>
56
+ </div>
57
+
58
+ <div className="flex justify-end gap-3 p-4 bg-bg-panel border-t border-border-primary">
59
+ <Button variant="outline" onClick={onClose}>
60
+ Cancel
61
+ </Button>
62
+ <Button
63
+ variant="danger"
64
+ onClick={() => onConfirm(reason, comment)}
65
+ disabled={!reason.trim()}
66
+ className="bg-red-600 hover:bg-red-700 text-white"
67
+ >
68
+ Confirm Rejection
69
+ </Button>
70
+ </div>
71
+ </div>
72
+ </div>
73
+ );
74
+ }
@@ -0,0 +1,160 @@
1
+ import * as React from "react";
2
+ import { ReportData } from "@/types";
3
+ import { Button } from "@/components/ui/basic";
4
+ import { Save, X, Bold, Italic, Underline, List, AlignLeft, AlignCenter, AlignRight } from "lucide-react";
5
+
6
+ interface ReportEditorProps {
7
+ report: ReportData;
8
+ onSave: (updatedReport: ReportData) => void;
9
+ onCancel: () => void;
10
+ }
11
+
12
+ export function ReportEditor({ report, onSave, onCancel }: ReportEditorProps) {
13
+ // Local state for editing fields
14
+ const [findings, setFindings] = React.useState(report.findings);
15
+ const [impression, setImpression] = React.useState(report.impression.join("\n"));
16
+ const [recommendations, setRecommendations] = React.useState(report.recommendations?.join("\n") || "");
17
+ const [urgency, setUrgency] = React.useState(report.urgency);
18
+
19
+ const handleSave = () => {
20
+ const updatedReport: ReportData = {
21
+ ...report,
22
+ findings: findings,
23
+ impression: impression.split("\n").filter(line => line.trim() !== ""),
24
+ recommendations: recommendations.split("\n").filter(line => line.trim() !== ""),
25
+ urgency: urgency
26
+ };
27
+ onSave(updatedReport);
28
+ };
29
+
30
+ const handleFindingChange = (index: number, field: 'observation' | 'anatomical_region', value: string) => {
31
+ const newFindings = [...findings];
32
+ newFindings[index] = { ...newFindings[index], [field]: value };
33
+ setFindings(newFindings);
34
+ };
35
+
36
+ return (
37
+ <div className="flex flex-col h-full bg-white animate-in fade-in duration-200">
38
+ {/* Word-like Toolbar */}
39
+ <div className="flex items-center justify-between px-4 py-2 bg-gray-100 border-b border-gray-300 shrink-0">
40
+ <div className="flex items-center gap-2">
41
+ <div className="flex bg-white border border-gray-300 rounded shadow-sm">
42
+ <ToolbarButton icon={<Bold size={16} />} title="Bold" />
43
+ <ToolbarButton icon={<Italic size={16} />} title="Italic" />
44
+ <ToolbarButton icon={<Underline size={16} />} title="Underline" />
45
+ </div>
46
+ <div className="w-px h-6 bg-gray-300 mx-1" />
47
+ <div className="flex bg-white border border-gray-300 rounded shadow-sm">
48
+ <ToolbarButton icon={<AlignLeft size={16} />} title="Align Left" />
49
+ <ToolbarButton icon={<AlignCenter size={16} />} title="Align Center" />
50
+ <ToolbarButton icon={<AlignRight size={16} />} title="Align Right" />
51
+ <ToolbarButton icon={<List size={16} />} title="Bullet List" />
52
+ </div>
53
+ </div>
54
+
55
+ <div className="flex items-center gap-2">
56
+ <Button variant="outline" size="sm" onClick={onCancel} className="bg-white hover:bg-gray-50 text-gray-700 border-gray-300">
57
+ <X size={16} className="mr-1.5" /> Cancel
58
+ </Button>
59
+ <Button variant="default" size="sm" onClick={handleSave} className="bg-blue-600 hover:bg-blue-700 text-white shadow-sm">
60
+ <Save size={16} className="mr-1.5" /> Save Changes
61
+ </Button>
62
+ </div>
63
+ </div>
64
+
65
+ {/* Editor Content - mimicking a document page */}
66
+ <div className="flex-1 overflow-y-auto bg-gray-200 p-8">
67
+ <div className="max-w-4xl mx-auto bg-white shadow-lg min-h-[800px] p-12 border border-gray-300 outline-none">
68
+
69
+ {/* Header Info (Read Only context) */}
70
+ <div className="mb-8 border-b border-gray-200 pb-4 opacity-70 pointer-events-none select-none">
71
+ <h1 className="text-2xl font-bold text-gray-900">{report.patient.name}</h1>
72
+ <p className="text-sm text-gray-500">{report.study.modality} • {report.study.examination}</p>
73
+ </div>
74
+
75
+ {/* Editable Sections */}
76
+ <div className="space-y-8">
77
+ {/* Findings Section */}
78
+ <section>
79
+ <h3 className="text-sm font-bold text-gray-900 uppercase tracking-wider mb-2 border-b border-gray-200 pb-1">Findings</h3>
80
+ <div className="space-y-4">
81
+ {findings.map((finding, idx) => (
82
+ <div key={idx} className="flex gap-2 items-start p-2 rounded hover:bg-gray-50 border border-transparent hover:border-gray-200 transition-colors">
83
+ <input
84
+ value={finding.anatomical_region}
85
+ onChange={(e) => handleFindingChange(idx, 'anatomical_region', e.target.value)}
86
+ className="font-bold text-gray-900 bg-transparent border-b border-gray-300 focus:border-blue-500 outline-none w-1/4"
87
+ placeholder="Region"
88
+ />
89
+ <textarea
90
+ value={finding.observation}
91
+ onChange={(e) => handleFindingChange(idx, 'observation', e.target.value)}
92
+ className="flex-1 text-gray-700 bg-transparent border border-gray-200 rounded p-1 focus:border-blue-500 outline-none resize-none"
93
+ rows={2}
94
+ placeholder="Observation..."
95
+ />
96
+ </div>
97
+ ))}
98
+ </div>
99
+ </section>
100
+
101
+ {/* Impression Section */}
102
+ <section>
103
+ <h3 className="text-sm font-bold text-gray-900 uppercase tracking-wider mb-2 border-b border-gray-200 pb-1">Impression</h3>
104
+ <textarea
105
+ value={impression}
106
+ onChange={(e) => setImpression(e.target.value)}
107
+ className="w-full min-h-[150px] p-2 text-lg font-medium text-gray-900 border border-gray-200 rounded focus:border-blue-500 outline-none resize-y"
108
+ placeholder="Enter impression..."
109
+ />
110
+ </section>
111
+
112
+ {/* Urgency Selector */}
113
+ <section className="bg-blue-50 p-4 rounded border border-blue-100">
114
+ <h3 className="text-xs font-bold text-blue-900 uppercase tracking-wider mb-2">Urgency</h3>
115
+ <div className="flex gap-4">
116
+ {(['Routine', 'Urgent', 'Critical'] as const).map((level) => (
117
+ <label key={level} className="flex items-center gap-2 cursor-pointer">
118
+ <input
119
+ type="radio"
120
+ name="urgency"
121
+ value={level}
122
+ checked={urgency === level}
123
+ onChange={() => setUrgency(level)}
124
+ className="text-blue-600 focus:ring-blue-500"
125
+ />
126
+ <span className={`text-sm font-medium ${level === 'Critical' ? 'text-red-600' :
127
+ level === 'Urgent' ? 'text-orange-600' : 'text-gray-700'
128
+ }`}>{level}</span>
129
+ </label>
130
+ ))}
131
+ </div>
132
+ </section>
133
+
134
+ {/* Recommendations Section */}
135
+ <section>
136
+ <h3 className="text-sm font-bold text-gray-900 uppercase tracking-wider mb-2 border-b border-gray-200 pb-1">Recommendations</h3>
137
+ <textarea
138
+ value={recommendations}
139
+ onChange={(e) => setRecommendations(e.target.value)}
140
+ className="w-full min-h-[100px] p-2 text-gray-700 border border-gray-200 rounded focus:border-blue-500 outline-none resize-y"
141
+ placeholder="Enter recommendations (one per line)..."
142
+ />
143
+ </section>
144
+ </div>
145
+ </div>
146
+ </div>
147
+ </div>
148
+ );
149
+ }
150
+
151
+ function ToolbarButton({ icon, title }: { icon: React.ReactNode, title: string }) {
152
+ return (
153
+ <button
154
+ title={title}
155
+ className="p-1.5 text-gray-600 hover:text-gray-900 hover:bg-gray-100 transition-colors rounded-sm"
156
+ >
157
+ {icon}
158
+ </button>
159
+ );
160
+ }