@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,311 @@
1
+ "use client";
2
+
3
+ import { useState, useRef, useEffect, useCallback } from "react";
4
+ import { ChatMessage as ChatMessageType, Reference, CopilotPatientContext, QuickAction, ActivityState } from "@/types/copilot";
5
+ import ChatMessage from "./ChatMessage";
6
+ import ChatHistoryPanel from "./ChatHistoryPanel";
7
+ import ActivityIndicator from "./ActivityIndicator";
8
+ import { Bot, Send, Plus, Trash2, Sparkles, FileText, GitCompare, Clock, Stethoscope } from "lucide-react";
9
+
10
+ interface CopilotPanelProps {
11
+ messages: ChatMessageType[];
12
+ isLoading: boolean;
13
+ activityState: ActivityState;
14
+ onSendMessage: (message: string) => void;
15
+ onReferenceClick: (ref: Reference) => void;
16
+ onExecuteActions?: (actions: any[]) => void;
17
+ onNewChat: () => void;
18
+ onLoadSession: (sessionId: string) => void;
19
+ patientContext: CopilotPatientContext;
20
+ findingsCount?: number;
21
+ }
22
+
23
+ const QUICK_ACTIONS: QuickAction[] = [
24
+ { label: "Summarize history", prompt: "Summarize this patient's report history", icon: "📋" },
25
+ { label: "Compare with previous", prompt: "Compare the current report with the previous one", icon: "🔄" },
26
+ { label: "Highlight findings", prompt: "Highlight the findings from the report on the image", icon: "🎯" },
27
+ { label: "Where is the lesion?", prompt: "Where is the lesion in this image?", icon: "🔍" },
28
+ { label: "Point to abnormality", prompt: "Point to the abnormal area", icon: "👆" },
29
+ { label: "Show suspicious region", prompt: "Highlight the suspicious region", icon: "⚠️" },
30
+ { label: "Overlay segmentation", prompt: "Make an overlay segmentation for it.", icon: "🎨" },
31
+ { label: "Clear AI findings", prompt: "Clear all AI findings", icon: "🧹" },
32
+ ];
33
+
34
+ export default function CopilotPanel({
35
+ messages,
36
+ isLoading,
37
+ activityState,
38
+ onSendMessage,
39
+ onReferenceClick,
40
+ onExecuteActions,
41
+ onNewChat,
42
+ onLoadSession,
43
+ patientContext,
44
+ findingsCount = 0,
45
+ }: CopilotPanelProps) {
46
+ const [inputValue, setInputValue] = useState("");
47
+ const [showHistory, setShowHistory] = useState(false);
48
+ const [isMounted, setIsMounted] = useState(false);
49
+ const messagesEndRef = useRef<HTMLDivElement>(null);
50
+ const inputRef = useRef<HTMLTextAreaElement>(null);
51
+
52
+ // Prevent hydration mismatch
53
+ useEffect(() => {
54
+ setIsMounted(true);
55
+ }, []);
56
+
57
+ // Auto-scroll to bottom when new messages arrive
58
+ useEffect(() => {
59
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
60
+ }, [messages, isLoading]);
61
+
62
+ // Auto-focus input
63
+ useEffect(() => {
64
+ inputRef.current?.focus();
65
+ }, []);
66
+
67
+ const handleSend = useCallback(() => {
68
+ if (!inputValue.trim() || isLoading) return;
69
+ onSendMessage(inputValue.trim());
70
+ setInputValue("");
71
+ // Reset textarea height
72
+ if (inputRef.current) {
73
+ inputRef.current.style.height = "auto";
74
+ }
75
+ }, [inputValue, isLoading, onSendMessage]);
76
+
77
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
78
+ if (e.key === "Enter" && !e.shiftKey) {
79
+ e.preventDefault();
80
+ handleSend();
81
+ }
82
+ };
83
+
84
+ // Auto-resize textarea
85
+ const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
86
+ setInputValue(e.target.value);
87
+ e.target.style.height = "auto";
88
+ e.target.style.height = Math.min(e.target.scrollHeight, 120) + "px";
89
+ };
90
+
91
+ return (
92
+ <div className="flex flex-col h-full relative overflow-hidden">
93
+ {/* Header */}
94
+ <div className="shrink-0 px-5 py-4 border-b border-border-primary bg-bg-surface">
95
+ <div className="flex items-center justify-between">
96
+ <div className="flex items-center gap-3">
97
+ <div className="w-9 h-9 rounded-xl bg-gradient-to-br from-emerald-500 to-cyan-500 flex items-center justify-center shadow-lg shadow-emerald-500/20">
98
+ <Bot size={18} className="text-white" />
99
+ </div>
100
+ <div>
101
+ <div className="flex items-center gap-2">
102
+ <h3 className="text-sm font-bold text-text-heading">AI Copilot</h3>
103
+ {findingsCount > 0 && (
104
+ <span className="flex items-center gap-1 text-[10px] font-bold text-red-400 bg-red-500/15 px-2 py-0.5 rounded-full">
105
+ <span className="w-1.5 h-1.5 rounded-full bg-red-400 animate-pulse" />
106
+ {findingsCount}
107
+ </span>
108
+ )}
109
+ </div>
110
+ <p className="text-[11px] text-text-muted">
111
+ {patientContext.patientName
112
+ ? `Patient: ${patientContext.patientName}`
113
+ : "Ready to assist"}
114
+ </p>
115
+ </div>
116
+ </div>
117
+ <div className="flex items-center gap-1.5">
118
+ <button
119
+ onClick={() => setShowHistory(true)}
120
+ className="p-2 rounded-lg text-text-muted hover:text-primary hover:bg-bg-panel transition-all"
121
+ title="Chat History"
122
+ >
123
+ <Clock size={16} />
124
+ </button>
125
+ <button
126
+ onClick={onNewChat}
127
+ className="p-2 rounded-lg text-text-muted hover:text-primary hover:bg-bg-panel transition-all"
128
+ title="New chat"
129
+ >
130
+ <Plus size={16} />
131
+ </button>
132
+ </div>
133
+ </div>
134
+ </div>
135
+
136
+ {/* History Panel Overlay */}
137
+ {showHistory && (
138
+ <ChatHistoryPanel
139
+ onClose={() => setShowHistory(false)}
140
+ onSelectSession={(id) => {
141
+ onLoadSession(id);
142
+ setShowHistory(false);
143
+ }}
144
+ />
145
+ )}
146
+
147
+ {/* Messages Area */}
148
+ <div className="flex-1 overflow-y-auto px-4 py-4 space-y-4">
149
+ {!isMounted ? (
150
+ <div className="flex-1 opacity-0" />
151
+ ) : messages.length === 0 ? (
152
+ <WelcomeMessage
153
+ onQuickAction={onSendMessage}
154
+ patientContext={patientContext}
155
+ />
156
+ ) : (
157
+ <>
158
+ {messages.map((msg) => (
159
+ <ChatMessage
160
+ key={msg.id}
161
+ message={msg}
162
+ onReferenceClick={onReferenceClick}
163
+ onExecuteActions={onExecuteActions}
164
+ />
165
+ ))}
166
+
167
+ {/* Streaming Activity Indicator */}
168
+ {(isLoading || activityState.isActive) && (
169
+ <ActivityIndicator activityState={activityState} />
170
+ )}
171
+ </>
172
+ )}
173
+ <div ref={messagesEndRef} />
174
+ </div>
175
+
176
+ {/* Quick Actions (shown when there's context) */}
177
+ {messages.length > 0 && patientContext.patientId && !isLoading && (
178
+ <div className="shrink-0 px-4 pb-2 flex gap-1.5 overflow-x-auto">
179
+ {QUICK_ACTIONS.map((action) => (
180
+ <button
181
+ key={action.label}
182
+ onClick={() => onSendMessage(action.prompt)}
183
+ className="shrink-0 text-xs px-3 py-1.5 rounded-full border border-border-card
184
+ text-text-secondary hover:text-primary hover:border-primary/30 hover:bg-primary/5
185
+ transition-all duration-150 whitespace-nowrap"
186
+ >
187
+ {action.icon} {action.label}
188
+ </button>
189
+ ))}
190
+ </div>
191
+ )}
192
+
193
+ {/* Input Area */}
194
+ <div className="shrink-0 px-4 py-3 border-t border-border-primary bg-bg-surface">
195
+ <div className="flex items-end gap-2 bg-bg-panel rounded-xl border border-border-card p-2 focus-within:border-primary/50 focus-within:ring-1 focus-within:ring-primary/20 transition-all">
196
+ <textarea
197
+ ref={inputRef}
198
+ value={inputValue}
199
+ onChange={handleInputChange}
200
+ onKeyDown={handleKeyDown}
201
+ placeholder="Ask about a patient, report, or scan..."
202
+ rows={1}
203
+ className="flex-1 bg-transparent text-sm text-text-primary placeholder-text-muted resize-none outline-none px-2 py-1.5 max-h-[120px]"
204
+ disabled={isLoading}
205
+ />
206
+ <button
207
+ onClick={handleSend}
208
+ disabled={!inputValue.trim() || isLoading}
209
+ className={`
210
+ shrink-0 p-2 rounded-lg transition-all duration-200
211
+ ${inputValue.trim() && !isLoading
212
+ ? "bg-primary text-white hover:bg-primary-hover shadow-md shadow-primary/25"
213
+ : "bg-border-card text-text-muted cursor-not-allowed"
214
+ }
215
+ `}
216
+ >
217
+ <Send size={16} />
218
+ </button>
219
+ </div>
220
+ <p className="text-[10px] text-text-muted mt-1.5 text-center opacity-60">
221
+ Press Enter to send • Shift+Enter for new line
222
+ </p>
223
+ </div>
224
+ </div>
225
+ );
226
+ }
227
+
228
+ // ─── Welcome Message ─────────────────────────────────────────────────────────
229
+ function WelcomeMessage({
230
+ onQuickAction,
231
+ patientContext,
232
+ }: {
233
+ onQuickAction: (prompt: string) => void;
234
+ patientContext: CopilotPatientContext;
235
+ }) {
236
+ return (
237
+ <div className="flex flex-col items-center justify-center h-full text-center px-6 py-8">
238
+ {/* Logo */}
239
+ <div className="w-16 h-16 rounded-2xl bg-gradient-to-br from-emerald-500 to-cyan-500 flex items-center justify-center mb-6 shadow-2xl shadow-emerald-500/20">
240
+ <Sparkles size={28} className="text-white" />
241
+ </div>
242
+
243
+ <h3 className="text-xl font-bold text-text-heading mb-2">
244
+ OmniRad AI Copilot
245
+ </h3>
246
+ <p className="text-sm text-text-muted mb-8 max-w-sm">
247
+ I can help you navigate patient reports, compare studies, summarize findings, and more.
248
+ </p>
249
+
250
+ {/* Suggestion Cards */}
251
+ <div className="grid grid-cols-2 gap-3 w-full max-w-sm">
252
+ <SuggestionCard
253
+ icon={<FileText size={18} />}
254
+ title="View Report"
255
+ description="Show me the report for patient..."
256
+ onClick={() => onQuickAction("Show me the latest report")}
257
+ />
258
+ <SuggestionCard
259
+ icon={<GitCompare size={18} />}
260
+ title="Compare Studies"
261
+ description="Compare current with previous"
262
+ onClick={() => onQuickAction("Compare current report with previous")}
263
+ />
264
+ <SuggestionCard
265
+ icon={<Clock size={18} />}
266
+ title="Patient History"
267
+ description="Summarize patient timeline"
268
+ onClick={() => onQuickAction("Summarize this patient's history")}
269
+ />
270
+ <SuggestionCard
271
+ icon={<Stethoscope size={18} />}
272
+ title="Clinical Query"
273
+ description="What changed from the last study?"
274
+ onClick={() => onQuickAction("What changed from the previous report?")}
275
+ />
276
+ </div>
277
+ </div>
278
+ );
279
+ }
280
+
281
+ function SuggestionCard({
282
+ icon,
283
+ title,
284
+ description,
285
+ onClick,
286
+ }: {
287
+ icon: React.ReactNode;
288
+ title: string;
289
+ description: string;
290
+ onClick: () => void;
291
+ }) {
292
+ return (
293
+ <button
294
+ onClick={onClick}
295
+ className="
296
+ group flex flex-col items-start gap-2 p-4 rounded-xl
297
+ bg-bg-panel border border-border-card
298
+ hover:border-primary/30 hover:bg-primary/5
299
+ transition-all duration-200 text-left
300
+ "
301
+ >
302
+ <span className="text-primary group-hover:scale-110 transition-transform duration-200">
303
+ {icon}
304
+ </span>
305
+ <div>
306
+ <p className="text-sm font-semibold text-text-heading">{title}</p>
307
+ <p className="text-xs text-text-muted mt-0.5">{description}</p>
308
+ </div>
309
+ </button>
310
+ );
311
+ }
@@ -0,0 +1,75 @@
1
+ "use client";
2
+
3
+ import type { FindingSummary, CopilotViewerRef } from "@/types/copilot-viewer";
4
+ import { Target, Trash2, Crosshair, X } from "lucide-react";
5
+
6
+ interface FindingsListProps {
7
+ findings: FindingSummary[];
8
+ viewerRef: CopilotViewerRef | null;
9
+ onClearAll: () => void;
10
+ }
11
+
12
+ export default function FindingsList({ findings, viewerRef, onClearAll }: FindingsListProps) {
13
+ if (findings.length === 0) return null;
14
+
15
+ const handleFocusFinding = (finding: FindingSummary) => {
16
+ if (!viewerRef) return;
17
+ if (finding.slice !== undefined) {
18
+ viewerRef.jumpToSlice(finding.slice);
19
+ }
20
+ };
21
+
22
+ return (
23
+ <div className="border-t border-border-primary bg-bg-surface shrink-0">
24
+ {/* Header */}
25
+ <div className="px-4 py-2 flex items-center justify-between">
26
+ <div className="flex items-center gap-2 text-xs font-semibold text-text-muted uppercase tracking-wider">
27
+ <Target size={14} className="text-red-400" />
28
+ AI Findings
29
+ <span className="bg-red-500/20 text-red-400 px-1.5 py-0.5 rounded-full text-[10px] font-bold">
30
+ {findings.length}
31
+ </span>
32
+ </div>
33
+ <button
34
+ onClick={onClearAll}
35
+ className="flex items-center gap-1 text-[10px] font-medium text-text-muted hover:text-red-400 transition-colors px-2 py-1 rounded-md hover:bg-red-500/10"
36
+ title="Clear all AI findings"
37
+ >
38
+ <Trash2 size={11} />
39
+ CLEAR
40
+ </button>
41
+ </div>
42
+
43
+ {/* Findings List */}
44
+ <div className="px-2 pb-2 space-y-0.5 max-h-[140px] overflow-auto">
45
+ {findings.map((finding, idx) => (
46
+ <button
47
+ key={finding.annotation_id || idx}
48
+ onClick={() => handleFocusFinding(finding)}
49
+ className="w-full flex items-center gap-3 px-3 py-2 rounded-lg text-sm text-left
50
+ text-text-primary hover:bg-red-500/5 hover:border-red-500/20
51
+ transition-all duration-150 group border border-transparent"
52
+ >
53
+ <Crosshair size={14} className="shrink-0 text-red-400 group-hover:scale-110 transition-transform" />
54
+ <div className="flex-1 min-w-0">
55
+ <div className="truncate font-medium text-text-heading text-[13px]">
56
+ {finding.name}
57
+ </div>
58
+ {finding.confidence !== undefined && finding.confidence > 0 && (
59
+ <div className="text-[11px] text-text-muted">
60
+ {Math.round(finding.confidence * 100)}% confidence
61
+ {finding.slice !== undefined && ` · Slice ${finding.slice + 1}`}
62
+ </div>
63
+ )}
64
+ </div>
65
+ <div className="flex items-center gap-1">
66
+ <span className="text-[9px] font-bold text-red-400 bg-red-500/10 px-1.5 py-0.5 rounded">
67
+ AI
68
+ </span>
69
+ </div>
70
+ </button>
71
+ ))}
72
+ </div>
73
+ </div>
74
+ );
75
+ }