@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.
- package/README.md +438 -0
- package/app/api/ai-config/route.ts +131 -0
- package/app/api/ai-config/test/route.ts +49 -0
- package/app/api/auth/auto-login/route.ts +66 -0
- package/app/api/auth/check/route.ts +17 -0
- package/app/api/auth/login/route.ts +72 -0
- package/app/api/auth/logout/route.ts +25 -0
- package/app/api/auth/me/route.ts +75 -0
- package/app/api/auth/password/route.ts +49 -0
- package/app/api/auth/setup/route.ts +63 -0
- package/app/api/auth/users/route.ts +100 -0
- package/app/api/auth/wipe/route.ts +27 -0
- package/app/api/compliance/anonymize/patient/[id]/route.ts +104 -0
- package/app/api/compliance/audit/route.ts +110 -0
- package/app/api/compliance/export/patient/[id]/route.ts +108 -0
- package/app/api/compliance/restrict/patient/[id]/route.ts +59 -0
- package/app/api/compliance/settings/route.ts +93 -0
- package/app/api/copilot/annotate/route.ts +94 -0
- package/app/api/copilot/chat/route.ts +238 -0
- package/app/api/copilot/history/route.ts +95 -0
- package/app/api/copilot/reports/route.ts +81 -0
- package/app/api/fhir/Bundle/report/[id]/route.ts +85 -0
- package/app/api/fhir/DiagnosticReport/[id]/route.ts +45 -0
- package/app/api/fhir/ImagingStudy/[id]/route.ts +57 -0
- package/app/api/fhir/Patient/[id]/route.ts +26 -0
- package/app/api/fhir/ServiceRequest/route.ts +85 -0
- package/app/api/fhir/config/route.ts +102 -0
- package/app/api/fhir/config/test-connection/route.ts +49 -0
- package/app/api/fhir/metadata/route.ts +51 -0
- package/app/api/pacs/metadata/route.ts +32 -0
- package/app/api/pacs/qido/instances/route.ts +39 -0
- package/app/api/pacs/qido/series/route.ts +38 -0
- package/app/api/pacs/qido/studies/route.ts +37 -0
- package/app/api/pacs/test/route.ts +30 -0
- package/app/api/pacs/wado/render/route.ts +51 -0
- package/app/api/patients/[id]/reports/route.ts +18 -0
- package/app/api/patients/[id]/route.ts +43 -0
- package/app/api/patients/merge/route.ts +57 -0
- package/app/api/patients/route.ts +67 -0
- package/app/api/patients/search/route.ts +25 -0
- package/app/api/reports/[id]/route.ts +84 -0
- package/app/api/reports/[id]/status/route.ts +87 -0
- package/app/api/reports/clear/route.ts +16 -0
- package/app/api/reports/route.ts +112 -0
- package/app/api/segmentation-config/route.ts +238 -0
- package/app/api/settings/route.ts +245 -0
- package/app/api/settings/test-supabase/route.ts +103 -0
- package/app/api/upload/route.ts +48 -0
- package/app/copilot/page.tsx +30 -0
- package/app/globals.css +141 -0
- package/app/history/page.tsx +242 -0
- package/app/icon.svg +3 -0
- package/app/layout.tsx +47 -0
- package/app/login/page.tsx +175 -0
- package/app/pacs/page.tsx +78 -0
- package/app/page.tsx +125 -0
- package/app/patients/[id]/page.tsx +315 -0
- package/app/patients/page.tsx +110 -0
- package/app/profile/page.tsx +208 -0
- package/app/reports/page.tsx +432 -0
- package/app/settings/page.tsx +454 -0
- package/app/setup/page.tsx +199 -0
- package/components/admin/AuditLogTable.tsx +293 -0
- package/components/copilot/ActivityIndicator.tsx +215 -0
- package/components/copilot/ChatHistoryPanel.tsx +140 -0
- package/components/copilot/ChatMessage.tsx +251 -0
- package/components/copilot/ClickableReference.tsx +40 -0
- package/components/copilot/CopilotCornerstoneViewer.tsx +562 -0
- package/components/copilot/CopilotPanel.tsx +311 -0
- package/components/copilot/FindingsList.tsx +75 -0
- package/components/copilot/ViewerPanel.tsx +460 -0
- package/components/copilot/WorkspaceLayout.tsx +398 -0
- package/components/dashboard/AIConfigPanel.tsx +339 -0
- package/components/dashboard/AppearancePanel.tsx +491 -0
- package/components/dashboard/ApprovalModal.tsx +163 -0
- package/components/dashboard/CollaborationPanel.tsx +134 -0
- package/components/dashboard/CopilotConfigPanel.tsx +337 -0
- package/components/dashboard/DicomViewer.tsx +645 -0
- package/components/dashboard/FhirIntegrationPanel.tsx +331 -0
- package/components/dashboard/FullReportOverlay.tsx +269 -0
- package/components/dashboard/ImageViewer.tsx +541 -0
- package/components/dashboard/PatientForm.tsx +597 -0
- package/components/dashboard/RejectionModal.tsx +74 -0
- package/components/dashboard/ReportEditor.tsx +160 -0
- package/components/dashboard/ReportTemplates.tsx +729 -0
- package/components/dashboard/ReportView.tsx +539 -0
- package/components/dashboard/SegmentationConfigPanel.tsx +490 -0
- package/components/dashboard/StudyPlaceholder.tsx +17 -0
- package/components/dashboard/SupabaseIntegrationPanel.tsx +345 -0
- package/components/dashboard/UserManagementPanel.tsx +272 -0
- package/components/layout/ClientLayout.tsx +39 -0
- package/components/layout/Header.tsx +20 -0
- package/components/layout/Sidebar.tsx +119 -0
- package/components/pacs/PacsImageViewerModal.tsx +121 -0
- package/components/pacs/PacsSearchFilters.tsx +117 -0
- package/components/pacs/PacsSeriesViewer.tsx +190 -0
- package/components/pacs/PacsStudyTable.tsx +113 -0
- package/components/patients/patient-card.tsx +117 -0
- package/components/patients/patient-header.tsx +122 -0
- package/components/patients/patient-search.tsx +137 -0
- package/components/patients/patient-timeline.tsx +153 -0
- package/components/settings/ComplianceSettingsPanel.tsx +278 -0
- package/components/settings/SecurityPanel.tsx +418 -0
- package/components/ui/badge.tsx +19 -0
- package/components/ui/basic.tsx +156 -0
- package/db/index.ts +350 -0
- package/db/migrations/0000_odd_quasimodo.sql +117 -0
- package/db/migrations/meta/0000_snapshot.json +778 -0
- package/db/migrations/meta/_journal.json +13 -0
- package/db/schema.ts +239 -0
- package/drizzle.config.ts +10 -0
- package/lib/api.ts +689 -0
- package/lib/auth.ts +22 -0
- package/lib/copilot/action-executor.ts +94 -0
- package/lib/copilot/action-types.ts +72 -0
- package/lib/copilot/coordinate-mapper.ts +84 -0
- package/lib/dicomImageExtractor.ts +103 -0
- package/lib/dicomMetadataParser.ts +111 -0
- package/lib/fhir/client.ts +25 -0
- package/lib/fhir/constants.ts +21 -0
- package/lib/fhir/diagnostic-report.ts +88 -0
- package/lib/fhir/helpers.ts +73 -0
- package/lib/fhir/imaging-study.ts +49 -0
- package/lib/fhir/patient.ts +55 -0
- package/lib/fhir/service-request.ts +85 -0
- package/lib/fhir.ts +6 -0
- package/lib/pacs/dicom-utils.ts +72 -0
- package/lib/pacs/dicomweb.ts +72 -0
- package/lib/pacs/server-utils.ts +37 -0
- package/lib/patients.ts +25 -0
- package/lib/pdfHelper.ts +119 -0
- package/lib/reportHtmlGenerator.ts +581 -0
- package/lib/security/audit.ts +180 -0
- package/lib/security/authz.ts +246 -0
- package/lib/security/phi-redaction.ts +156 -0
- package/lib/security/rate-limit.ts +106 -0
- package/lib/security/secrets.ts +179 -0
- package/lib/supabase.ts +72 -0
- package/lib/utils.ts +6 -0
- package/next.config.ts +35 -0
- package/package.json +76 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/logo.svg +8 -0
- package/public/next.svg +1 -0
- package/public/omnirad-favicon.svg +8 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/tsconfig.json +34 -0
- package/types/copilot-viewer.ts +155 -0
- package/types/copilot.ts +105 -0
- package/types/fhir.ts +21 -0
- package/types/html2pdf.d.ts +20 -0
- package/types/index.ts +139 -0
- package/types/pacs.ts +41 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OmniRad Secret Management Module
|
|
3
|
+
*
|
|
4
|
+
* Handles encryption, hashing, masking, and secure storage of secrets
|
|
5
|
+
* like API keys, PACS passwords, and integration tokens.
|
|
6
|
+
*/
|
|
7
|
+
import { createCipheriv, createDecipheriv, randomBytes, createHash } from "crypto";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
|
|
11
|
+
// ─── Encryption Key Management ──────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
const ALGORITHM = "aes-256-gcm";
|
|
14
|
+
const KEY_LENGTH = 32; // 256 bits
|
|
15
|
+
const IV_LENGTH = 12; // 96 bits for GCM
|
|
16
|
+
const AUTH_TAG_LENGTH = 16;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Gets or creates the master encryption key.
|
|
20
|
+
* Priority: 1) OMNIRAD_ENCRYPTION_KEY env var, 2) Auto-generated key file
|
|
21
|
+
*/
|
|
22
|
+
function getEncryptionKey(): Buffer {
|
|
23
|
+
// Check environment variable first
|
|
24
|
+
const envKey = process.env.OMNIRAD_ENCRYPTION_KEY;
|
|
25
|
+
if (envKey) {
|
|
26
|
+
// If it's hex-encoded
|
|
27
|
+
if (/^[0-9a-fA-F]{64}$/.test(envKey)) {
|
|
28
|
+
return Buffer.from(envKey, "hex");
|
|
29
|
+
}
|
|
30
|
+
// Hash the env key to get exactly 32 bytes
|
|
31
|
+
return createHash("sha256").update(envKey).digest();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Auto-generate and store key in data directory
|
|
35
|
+
const dataDir = path.join(process.cwd(), "data");
|
|
36
|
+
const keyFile = path.join(dataDir, ".encryption_key");
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
if (fs.existsSync(keyFile)) {
|
|
40
|
+
const keyHex = fs.readFileSync(keyFile, "utf-8").trim();
|
|
41
|
+
return Buffer.from(keyHex, "hex");
|
|
42
|
+
}
|
|
43
|
+
} catch {
|
|
44
|
+
// File doesn't exist or can't be read — generate new key
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Generate new key
|
|
48
|
+
const newKey = randomBytes(KEY_LENGTH);
|
|
49
|
+
|
|
50
|
+
// Ensure data directory exists
|
|
51
|
+
if (!fs.existsSync(dataDir)) {
|
|
52
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Write key file with restrictive permissions
|
|
56
|
+
fs.writeFileSync(keyFile, newKey.toString("hex"), { mode: 0o600 });
|
|
57
|
+
|
|
58
|
+
return newKey;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Cache the key in memory
|
|
62
|
+
let _cachedKey: Buffer | null = null;
|
|
63
|
+
function getCachedKey(): Buffer {
|
|
64
|
+
if (!_cachedKey) {
|
|
65
|
+
_cachedKey = getEncryptionKey();
|
|
66
|
+
}
|
|
67
|
+
return _cachedKey;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ─── Encryption / Decryption ─────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Encrypts a plaintext string using AES-256-GCM.
|
|
74
|
+
* Returns a string in format: iv:authTag:ciphertext (all hex-encoded)
|
|
75
|
+
*/
|
|
76
|
+
export function encryptSecret(plaintext: string): string {
|
|
77
|
+
if (!plaintext) return "";
|
|
78
|
+
|
|
79
|
+
const key = getCachedKey();
|
|
80
|
+
const iv = randomBytes(IV_LENGTH);
|
|
81
|
+
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
82
|
+
|
|
83
|
+
let encrypted = cipher.update(plaintext, "utf8", "hex");
|
|
84
|
+
encrypted += cipher.final("hex");
|
|
85
|
+
const authTag = cipher.getAuthTag();
|
|
86
|
+
|
|
87
|
+
return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Decrypts a ciphertext string encrypted by encryptSecret.
|
|
92
|
+
* Returns the original plaintext.
|
|
93
|
+
*/
|
|
94
|
+
export function decryptSecret(ciphertext: string): string {
|
|
95
|
+
if (!ciphertext) return "";
|
|
96
|
+
|
|
97
|
+
// If it doesn't look encrypted (no colons), return as-is for backward compat
|
|
98
|
+
if (!ciphertext.includes(":")) return ciphertext;
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const parts = ciphertext.split(":");
|
|
102
|
+
if (parts.length !== 3) return ciphertext; // Not encrypted format
|
|
103
|
+
|
|
104
|
+
const [ivHex, authTagHex, encryptedHex] = parts;
|
|
105
|
+
const key = getCachedKey();
|
|
106
|
+
const iv = Buffer.from(ivHex, "hex");
|
|
107
|
+
const authTag = Buffer.from(authTagHex, "hex");
|
|
108
|
+
|
|
109
|
+
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
|
110
|
+
decipher.setAuthTag(authTag);
|
|
111
|
+
|
|
112
|
+
let decrypted = decipher.update(encryptedHex, "hex", "utf8");
|
|
113
|
+
decrypted += decipher.final("utf8");
|
|
114
|
+
|
|
115
|
+
return decrypted;
|
|
116
|
+
} catch {
|
|
117
|
+
// If decryption fails, return as-is (might be a plaintext from before encryption was added)
|
|
118
|
+
return ciphertext;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ─── Hashing (for verification-only tokens) ──────────────────────────────────
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Creates a SHA-256 hash of a token (for verification-only use cases).
|
|
126
|
+
* Use this for integration tokens where we only need to verify, not recover.
|
|
127
|
+
*/
|
|
128
|
+
export function hashToken(token: string): string {
|
|
129
|
+
return createHash("sha256").update(token).digest("hex");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Verifies a token against its hash.
|
|
134
|
+
*/
|
|
135
|
+
export function verifyToken(token: string, hash: string): boolean {
|
|
136
|
+
const computed = hashToken(token);
|
|
137
|
+
// Constant-time comparison to prevent timing attacks
|
|
138
|
+
if (computed.length !== hash.length) return false;
|
|
139
|
+
let result = 0;
|
|
140
|
+
for (let i = 0; i < computed.length; i++) {
|
|
141
|
+
result |= computed.charCodeAt(i) ^ hash.charCodeAt(i);
|
|
142
|
+
}
|
|
143
|
+
return result === 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ─── Masking ─────────────────────────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Masks a secret value for display, showing only the last 4 characters.
|
|
150
|
+
* Returns "********last4" format.
|
|
151
|
+
*/
|
|
152
|
+
export function maskSecret(value: string | null | undefined): string {
|
|
153
|
+
if (!value || value.length === 0) return "";
|
|
154
|
+
if (value.length <= 4) return "********";
|
|
155
|
+
|
|
156
|
+
const last4 = value.slice(-4);
|
|
157
|
+
return `********${last4}`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Returns an object indicating whether a secret is set and its masked value.
|
|
162
|
+
* Use this when building API responses that reference secrets.
|
|
163
|
+
*/
|
|
164
|
+
export function secretSummary(value: string | null | undefined): { hasSecret: boolean; maskedValue: string } {
|
|
165
|
+
return {
|
|
166
|
+
hasSecret: !!value && value.length > 0,
|
|
167
|
+
maskedValue: maskSecret(value),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ─── Token Generation ────────────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Generates a cryptographically secure random token.
|
|
175
|
+
* Default length is 48 bytes (96 hex chars).
|
|
176
|
+
*/
|
|
177
|
+
export function generateSecureToken(byteLength: number = 48): string {
|
|
178
|
+
return randomBytes(byteLength).toString("hex");
|
|
179
|
+
}
|
package/lib/supabase.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
|
|
2
|
+
import { createClient, SupabaseClient } from '@supabase/supabase-js';
|
|
3
|
+
|
|
4
|
+
// Helper to validate a proper HTTP/HTTPS URL
|
|
5
|
+
function isValidHttpUrl(str: string): boolean {
|
|
6
|
+
try {
|
|
7
|
+
const url = new URL(str);
|
|
8
|
+
return url.protocol === 'http:' || url.protocol === 'https:';
|
|
9
|
+
} catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Cache: once we fetch the config from the API, we store it here
|
|
15
|
+
let cachedConfig: { supabaseUrl: string; supabaseAnonKey: string } | null = null;
|
|
16
|
+
let cachedClient: SupabaseClient | null = null;
|
|
17
|
+
let configLoaded = false;
|
|
18
|
+
|
|
19
|
+
// Async function to ensure the config is loaded from the SQLite API
|
|
20
|
+
export async function ensureSupabaseConfig() {
|
|
21
|
+
if (configLoaded) return;
|
|
22
|
+
configLoaded = true;
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const res = await fetch('/api/settings?type=config');
|
|
26
|
+
if (res.ok) {
|
|
27
|
+
const data = await res.json();
|
|
28
|
+
if (data.supabaseUrl?.trim() && data.supabaseAnonKey?.trim()) {
|
|
29
|
+
cachedConfig = {
|
|
30
|
+
supabaseUrl: data.supabaseUrl.trim(),
|
|
31
|
+
supabaseAnonKey: data.supabaseAnonKey.trim(),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} catch (e) {
|
|
36
|
+
console.warn("[OmniRad] Could not fetch Supabase config from API:", e);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Synchronous getter — uses cached config or env vars
|
|
41
|
+
export const getSupabaseClient = (): SupabaseClient | null => {
|
|
42
|
+
// If we already have a cached client, return it
|
|
43
|
+
if (cachedClient) return cachedClient;
|
|
44
|
+
|
|
45
|
+
let supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
46
|
+
let supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
|
47
|
+
|
|
48
|
+
// Override with cached config from API (if available)
|
|
49
|
+
if (cachedConfig) {
|
|
50
|
+
supabaseUrl = cachedConfig.supabaseUrl;
|
|
51
|
+
supabaseAnonKey = cachedConfig.supabaseAnonKey;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (supabaseUrl && supabaseAnonKey && isValidHttpUrl(supabaseUrl)) {
|
|
55
|
+
try {
|
|
56
|
+
cachedClient = createClient(supabaseUrl, supabaseAnonKey);
|
|
57
|
+
return cachedClient;
|
|
58
|
+
} catch (e) {
|
|
59
|
+
console.error("[OmniRad] Failed to create Supabase client:", e);
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return null;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Reset cached client (e.g. when settings change)
|
|
68
|
+
export const resetSupabaseClient = () => {
|
|
69
|
+
cachedClient = null;
|
|
70
|
+
cachedConfig = null;
|
|
71
|
+
configLoaded = false;
|
|
72
|
+
};
|
package/lib/utils.ts
ADDED
package/next.config.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { NextConfig } from "next";
|
|
2
|
+
|
|
3
|
+
const nextConfig: NextConfig = {
|
|
4
|
+
output: "standalone",
|
|
5
|
+
outputFileTracingRoot: process.cwd(),
|
|
6
|
+
webpack: (config) => {
|
|
7
|
+
config.experiments = {
|
|
8
|
+
...config.experiments,
|
|
9
|
+
asyncWebAssembly: true,
|
|
10
|
+
layers: true,
|
|
11
|
+
};
|
|
12
|
+
/* Cornerstone3D wasm file loading */
|
|
13
|
+
config.module.rules.push({
|
|
14
|
+
test: /\.wasm$/,
|
|
15
|
+
type: "asset/resource",
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/* Stub out Node.js built-ins that Cornerstone3D codec decoders reference */
|
|
19
|
+
config.resolve.fallback = {
|
|
20
|
+
...config.resolve.fallback,
|
|
21
|
+
fs: false,
|
|
22
|
+
path: false,
|
|
23
|
+
crypto: false,
|
|
24
|
+
stream: false,
|
|
25
|
+
os: false,
|
|
26
|
+
http: false,
|
|
27
|
+
https: false,
|
|
28
|
+
zlib: false,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return config;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default nextConfig;
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@omniradiology/omnirad",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "OmniRad — AI-powered radiology platform with DICOM viewer and intelligent reporting",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"radiology",
|
|
7
|
+
"dicom",
|
|
8
|
+
"medical-imaging",
|
|
9
|
+
"ai",
|
|
10
|
+
"healthcare"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"author": "OmniRad Team",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/omniradiology/omnirad.git"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"app",
|
|
20
|
+
"components",
|
|
21
|
+
"lib",
|
|
22
|
+
"types",
|
|
23
|
+
"public",
|
|
24
|
+
"db",
|
|
25
|
+
"data",
|
|
26
|
+
"next.config.ts",
|
|
27
|
+
"tsconfig.json",
|
|
28
|
+
"drizzle.config.ts"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"dev": "concurrently -k -n \"NEXT,AI\" -c \"cyan,magenta\" \"npm run dev:next\" \"npm run dev:ai\"",
|
|
32
|
+
"dev:next": "next dev --webpack",
|
|
33
|
+
"dev:ai": "cd ai_service && python -m uv run main.py",
|
|
34
|
+
"build": "next build --webpack",
|
|
35
|
+
"start": "next start",
|
|
36
|
+
"lint": "eslint",
|
|
37
|
+
"postinstall": "node scripts/patch-html2canvas.js"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@cornerstonejs/core": "^4.20.0",
|
|
41
|
+
"@cornerstonejs/dicom-image-loader": "^4.20.0",
|
|
42
|
+
"@cornerstonejs/tools": "^4.20.0",
|
|
43
|
+
"@supabase/supabase-js": "^2.95.3",
|
|
44
|
+
"@types/bcryptjs": "^2.4.6",
|
|
45
|
+
"bcryptjs": "^3.0.3",
|
|
46
|
+
"better-sqlite3": "^12.8.0",
|
|
47
|
+
"clsx": "^2.1.1",
|
|
48
|
+
"dicom-parser": "^1.8.21",
|
|
49
|
+
"drizzle-orm": "^0.45.1",
|
|
50
|
+
"fhir-kit-client": "^2.0.2",
|
|
51
|
+
"html2pdf.js": "^0.14.0",
|
|
52
|
+
"lucide-react": "^0.563.0",
|
|
53
|
+
"next": "16.1.6",
|
|
54
|
+
"react": "19.2.3",
|
|
55
|
+
"react-dom": "19.2.3",
|
|
56
|
+
"react-markdown": "^10.1.0",
|
|
57
|
+
"react-resizable-panels": "^4.6.2",
|
|
58
|
+
"react-signature-canvas": "^1.1.0-alpha.2",
|
|
59
|
+
"remark-gfm": "^4.0.1",
|
|
60
|
+
"tailwind-merge": "^3.4.0"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@tailwindcss/postcss": "^4",
|
|
64
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
65
|
+
"@types/fhir": "^0.0.44",
|
|
66
|
+
"@types/node": "^20",
|
|
67
|
+
"@types/react": "^19",
|
|
68
|
+
"@types/react-dom": "^19",
|
|
69
|
+
"concurrently": "^9.2.1",
|
|
70
|
+
"drizzle-kit": "^0.31.10",
|
|
71
|
+
"eslint": "^9",
|
|
72
|
+
"eslint-config-next": "16.1.6",
|
|
73
|
+
"tailwindcss": "^4",
|
|
74
|
+
"typescript": "^5"
|
|
75
|
+
}
|
|
76
|
+
}
|
package/public/file.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
package/public/globe.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|