Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
466b4f5
Create validation.yml
MarSH-Up Dec 4, 2024
3626493
Create node.js.yml
MarSH-Up Jan 13, 2025
2b5a8d5
Merge pull request #77 from NeuroJSON/staging
elainefan331 Jul 24, 2025
c79b870
Merge pull request #80 from NeuroJSON/staging
elainefan331 Aug 22, 2025
db534c0
Merge pull request #85 from NeuroJSON/staging
elainefan331 Aug 25, 2025
3af6b27
Merge pull request #87 from NeuroJSON/staging
elainefan331 Aug 26, 2025
a7c61fd
Merge pull request #91 from NeuroJSON/staging
elainefan331 Sep 8, 2025
7282894
Merge pull request #94 from NeuroJSON/staging
elainefan331 Sep 19, 2025
e4ad801
Merge pull request #100 from NeuroJSON/staging
elainefan331 Sep 25, 2025
686dbcb
Merge pull request #102 from NeuroJSON/staging
elainefan331 Oct 3, 2025
1bee111
Merge pull request #108 from NeuroJSON/staging
elainefan331 Oct 23, 2025
151e6ad
Merge pull request #110 from NeuroJSON/staging
elainefan331 Nov 18, 2025
f89a250
Merge pull request #113 from NeuroJSON/staging
elainefan331 Jan 14, 2026
aee4182
Merge pull request #115 from NeuroJSON/staging
elainefan331 Jan 15, 2026
fbd093b
Merge pull request #117 from NeuroJSON/staging
elainefan331 Jan 20, 2026
54ae3af
Merge pull request #119 from NeuroJSON/staging
elainefan331 Jan 21, 2026
32268ba
Merge pull request #121 from NeuroJSON/staging
elainefan331 Jan 21, 2026
9a94cc1
Merge pull request #123 from NeuroJSON/staging
elainefan331 Mar 24, 2026
97afe93
Merge pull request #129 from NeuroJSON/staging
elainefan331 Jun 1, 2026
9a57165
Merge pull request #131 from NeuroJSON/staging
elainefan331 Jun 1, 2026
67354bb
feat(autobidsify): parse .mat v7.3 files and rename local AI URL label
elainefan331 Jun 2, 2026
5ac48c8
fix(llm): route local-ollama to localhost, not backend proxy
elainefan331 Jun 2, 2026
69a4dd8
fix(llm): rename local-ollama provider to local-ai for clarity
elainefan331 Jun 2, 2026
3bb9d77
refactor(ollama): rename chat endpoint to bidsify
elainefan331 Jun 2, 2026
ec920b6
feat(llm): fetch local AI models dynamically
elainefan331 Jun 2, 2026
8b67e8c
revert(llm): remove local model fetch button — blocked by browser COR…
elainefan331 Jun 2, 2026
82a7d3e
feat(dropzone): add Browse Folder button with full folder tree struct…
elainefan331 Jun 3, 2026
bc49537
feat(connector): route local-ai through connector when available; det…
elainefan331 Jun 3, 2026
f69e584
feat(autobidsify): replace connector with standalone executor; remove…
elainefan331 Jun 8, 2026
ef8580d
fix(autobidsify): simplify local AI origin hint to one-line info alert
elainefan331 Jun 8, 2026
7b1103e
feat(autobidsify): add Download Executor button with OS-aware GitHub …
elainefan331 Jun 8, 2026
3a4097b
feat(autobidsify): add Next Steps card with download buttons and exec…
elainefan331 Jun 8, 2026
9d19e8c
feat(autobidsify): rename preview button; remove header executor button
elainefan331 Jun 8, 2026
3f0da42
feat(about): add autobidsify demo video to about page
elainefan331 Jun 9, 2026
ad2c8fe
fix(llmpanel): prevent content collapse on resize by adding minHeight…
elainefan331 Jun 9, 2026
1fb9d3b
feat(filetree): move root directory path field into Virtual File Syst…
elainefan331 Jun 9, 2026
f46f6da
feat(filetree): show typed root path in directory alert message
elainefan331 Jun 9, 2026
941a980
fix(export): prevent duplicate folder name in _sourcePath when baseDi…
elainefan331 Jun 9, 2026
8ff658c
fix(export): handle root folder exact match in resolveSourcePath
elainefan331 Jun 9, 2026
2b24ea9
feat(autobidsify): rename executor to converter; update button labels…
elainefan331 Jun 9, 2026
ba5f835
fix(email): catch Ethereal API failure in dev so server doesn't crash
elainefan331 Jun 9, 2026
1561140
feat(autobidsify): add how-to tooltip in project workspace; rename pa…
elainefan331 Jun 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs

name: Node.js CI

on:
push:
branches: [ "dev", "main" ]
pull_request:
branches: [ "dev", "main" ]

jobs:
build:

runs-on: ubuntu-latest

strategy:
matrix:
node-version: [18.x, 20.x, 22.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'yarn' # Cache yarn dependencies
- run: yarn install
- run: yarn build --if-present
- run: yarn test
43 changes: 43 additions & 0 deletions .github/workflows/validation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Validate NeuroJson_io Project

on:
push:
branches:
- dev
- main
pull_request:
branches:
- dev
- main

jobs:
validate:
runs-on: ubuntu-latest

steps:
- name: Check out the repository
uses: actions/checkout@v3

- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '18'

- name: Install dependencies
run: yarn install

- name: Compile TypeScript
run: yarn build

- name: Run Linter
run: yarn lint

# - name: Check for exposed environment variables
# run: |
# if [ -f ".env" ]; then
# echo "Error: .env file found. Environment variables should not be exposed." >&2
# exit 1
# fi

- name: Run Security Audit
run: yarn audit --level moderate
29 changes: 17 additions & 12 deletions backend/services/email.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,23 @@ class EmailService {
}

async createTestTransporter() {
const testAccount = await nodemailer.createTestAccount();
this.transporter = nodemailer.createTransport({
host: "smtp.ethereal.email",
port: 587,
secure: false,
auth: {
user: testAccount.user,
pass: testAccount.pass,
},
});
console.log("📧 Using Ethereal email for development");
console.log("Preview emails at: https://ethereal.email");
try {
const testAccount = await nodemailer.createTestAccount();
this.transporter = nodemailer.createTransport({
host: "smtp.ethereal.email",
port: 587,
secure: false,
auth: {
user: testAccount.user,
pass: testAccount.pass,
},
});
console.log("📧 Using Ethereal email for development");
console.log("Preview emails at: https://ethereal.email");
} catch (err) {
console.warn("⚠️ Ethereal unavailable, email disabled in dev:", err.message);
this.transporter = null;
}
}

async sendVerificationEmail(user, token) {
Expand Down
2 changes: 1 addition & 1 deletion backend/src/routes/ollama.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const router = express.Router();
const { proxyChat, getTags } = require("../controllers/ollama.controller");
const { requireAuth } = require("../middleware/auth.middleware");

router.post("/chat", requireAuth, proxyChat);
router.post("/bidsify", requireAuth, proxyChat);
// router.get("/tags", requireAuth, getTags);

module.exports = router;
68 changes: 46 additions & 22 deletions src/components/User/Dashboard/DatasetOrganizer/DropZone.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
// src/components/DatasetOrganizer/DropZone.tsx
import { processFile, processFolder, processZip } from "./utils/fileProcessors";
import { processFile, processFolder, processFolderFromFiles, processZip } from "./utils/fileProcessors";
import { CloudUpload, Add, CheckCircle } from "@mui/icons-material";
import {
Box,
Typography,
Paper,
Button,
TextField,
CircularProgress,
} from "@mui/material";
import { Colors } from "design/theme";
Expand Down Expand Up @@ -38,6 +37,7 @@ const DropZone: React.FC<DropZoneProps> = ({
const [isDragging, setIsDragging] = useState(false);
const [isProcessing, setIsProcessing] = useState(false); // ← add
const fileInputRef = useRef<HTMLInputElement>(null);
const folderInputRef = useRef<HTMLInputElement>(null);
// const [basePath, setBasePath] = useState<string>(""); // change

const handleDragOver = (e: React.DragEvent) => {
Expand Down Expand Up @@ -117,6 +117,24 @@ const DropZone: React.FC<DropZoneProps> = ({
}
};

const handleFolderSelect = async (e: React.ChangeEvent<HTMLInputElement>) => {
const selectedFiles = Array.from(e.target.files || []);
if (selectedFiles.length === 0) return;
setIsProcessing(true);

// Auto-detect root folder name from webkitRelativePath (e.g. "myDataset/sub-01/file.nii")
const rootFolder = selectedFiles[0].webkitRelativePath.split("/")[0];
if (rootFolder && !baseDirectoryPath) setBaseDirectoryPath(rootFolder);

try {
const items = await processFolderFromFiles(selectedFiles, baseDirectoryPath);
setFiles((prev) => [...prev, ...items]);
} finally {
setIsProcessing(false);
e.target.value = "";
}
};

return (
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
{/* Show file count if files exist */}
Expand All @@ -136,7 +154,7 @@ const DropZone: React.FC<DropZoneProps> = ({
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onClick={() => fileInputRef.current?.click()}
onClick={(e) => { if (e.target === e.currentTarget) fileInputRef.current?.click(); }}
sx={{
border: `2px dashed ${isDragging ? Colors.purple : Colors.lightGray}`,
borderRadius: 2,
Expand Down Expand Up @@ -198,13 +216,24 @@ const DropZone: React.FC<DropZoneProps> = ({
📁 Folders • 🗜️ ZIP files • 📄 Documents (.json, .txt, .md) • 📊
Office (.docx, .pdf, .xlsx)
</Typography>
<Button
variant="outlined"
startIcon={<Add />}
sx={{ borderColor: Colors.purple, color: Colors.purple }}
>
Or Click to Browse
</Button>
<Box sx={{ display: "flex", gap: 1, justifyContent: "center" }}>
<Button
variant="outlined"
startIcon={<Add />}
onClick={(e) => { e.stopPropagation(); fileInputRef.current?.click(); }}
sx={{ borderColor: Colors.purple, color: Colors.purple }}
>
Browse Files
</Button>
<Button
variant="outlined"
startIcon={<Add />}
onClick={(e) => { e.stopPropagation(); folderInputRef.current?.click(); }}
sx={{ borderColor: Colors.purple, color: Colors.purple }}
>
Browse Folder
</Button>
</Box>
</>
)}
<input
Expand All @@ -215,19 +244,14 @@ const DropZone: React.FC<DropZoneProps> = ({
onChange={handleFileSelect}
accept=".nii,.nii.gz,.snirf,.h5,.hdf5,.jnii,.jmsh,.json,.txt,.md,.zip,.docx,.pdf,.xlsx,.xls,.mat,.dcm,.nirs"
/>
<input
ref={folderInputRef}
type="file"
hidden
onChange={handleFolderSelect}
{...{ webkitdirectory: "", mozdirectory: "" } as any}
/>
</Paper>
<TextField
label="Directory Path (actual data path)"
placeholder="example: /Users/username/Desktop/Downloads"
// value={basePath} // change
// onChange={(e) => setBasePath(e.target.value)} //change
value={baseDirectoryPath} // ✅ CHANGE: Use prop
onChange={(e) => setBaseDirectoryPath(e.target.value)} // ✅ CHANGE: Use prop setter
fullWidth
size="small"
sx={{ mb: 2 }}
helperText="Enter the folder path where these files are located"
/>
</Box>
);
};
Expand Down
21 changes: 20 additions & 1 deletion src/components/User/Dashboard/DatasetOrganizer/FileTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
Paper,
Button,
TextField,
Alert,
Dialog,
DialogTitle,
DialogContent,
Expand All @@ -37,6 +38,8 @@ interface FileTreeProps {
setSelectedIds: React.Dispatch<React.SetStateAction<Set<string>>>;
expandedIds: Set<string>;
setExpandedIds: React.Dispatch<React.SetStateAction<Set<string>>>;
baseDirectoryPath: string;
setBaseDirectoryPath: (path: string) => void;
}

const FileTree: React.FC<FileTreeProps> = ({
Expand All @@ -46,6 +49,8 @@ const FileTree: React.FC<FileTreeProps> = ({
setSelectedIds,
expandedIds,
setExpandedIds,
baseDirectoryPath,
setBaseDirectoryPath,
}) => {
const [noteDialogOpen, setNoteDialogOpen] = useState(false);
const [editingNoteId, setEditingNoteId] = useState<string | null>(null);
Expand Down Expand Up @@ -521,6 +526,20 @@ const FileTree: React.FC<FileTreeProps> = ({
// alignItems: "center",
}}
>
<TextField
label="Root Directory Path"
placeholder="/Users/username/Desktop/my-dataset"
value={baseDirectoryPath}
onChange={(e) => setBaseDirectoryPath(e.target.value)}
size="small"
sx={{ mb: 0.75 }}
fullWidth
/>
<Alert severity="info" sx={{ py: 0.5, mb: 1.5, fontSize: "0.75rem" }}>
{baseDirectoryPath
? <>All dropped files must be inside <Box component="span" sx={{ fontFamily: "monospace", fontWeight: 600 }}>{baseDirectoryPath}</Box> on your machine.</>
: "All dropped files must be inside this root folder on your machine."}
</Alert>
<Box>
<Typography variant="subtitle2" fontWeight={800}>
Virtual File System
Expand Down Expand Up @@ -635,7 +654,7 @@ const FileTree: React.FC<FileTreeProps> = ({
variant="body2"
sx={{ color: Colors.darkPurple, fontWeight: 400 }}
>
BIDS Conversion Package Preview
BIDS Conversion Bundle Preview
</Typography>
</Box>
{outputFiles
Expand Down
Loading
Loading