diff --git a/.gitignore b/.gitignore index 74c2474..ac1261b 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,6 @@ pytorch_connectomics/ .coverage coverage.xml dist/ -.env -.env.* + *.log .github/workflows/docker-test.yml diff --git a/COPILOT.md b/COPILOT.md new file mode 100644 index 0000000..f8cdfc1 --- /dev/null +++ b/COPILOT.md @@ -0,0 +1,86 @@ +**Project Summary** +- **Name:** pytc-client — Desktop client and services for Pytorch Connectomics +- **Purpose:** Electron + React desktop client that interacts with FastAPI backend services and the `pytorch_connectomics` library for connectomics workflows. + +**Top-level Structure** +- **`client/`**: Electron + React frontend (CRA). Important files: + - `client/package.json` — frontend scripts (`start`, `build`, `electron`). + - `client/main.js` — Electron entrypoint. + - `client/src/` — React source; components in `client/src/components/`. +- **`server_api/`**: FastAPI application (API server) + - `server_api/main.py` — API entrypoint. + - `server_api/requirements.txt` — Python deps for API. +- **`server_pytc/`**: PyTC worker service used for model inference or background tasks + - `server_pytc/main.py` — worker entrypoint. +- **`pytorch_connectomics/`**: Library (local package) with models, configs, and utilities used by the worker. + - `pytorch_connectomics/setup.py` — library install entry. + - `pytorch_connectomics/configs/` — example YAML config files. +- **`docker/`**: Docker resources for containerized backend; root `docker-compose.yaml` / `Dockerfile` exist. +- **`scripts/`**: Helper scripts + - `scripts/bootstrap.sh` / `scripts/bootstrap.ps1` — one-time install/bootstrap + - `start.sh` / `start.bat` — start the full stack (servers + Electron client) +- **`docs/`, `notebooks/`, `tests/`**: documentation, Jupyter notebooks, and unit tests respectively. + +**Important Root Files** +- `package.json` (root): convenience npm scripts added to operate on the `client/` folder from repository root. +- `README.md`: project setup and running instructions. + +**Common Developer Commands** +- Bootstrap (one-time): +```bash +./scripts/bootstrap.sh # macOS / Linux +scripts\bootstrap.ps1 # Windows PowerShell +``` +- Start full stack (desktop + services): +```bash +./start.sh # macOS / Linux +start.bat # Windows CMD +``` +- Build frontend from repo root (convenience scripts added): +```bash +npm run client:install # install dependencies inside `client/` +npm run client:build # build production frontend in `client/build` +npm run client:dev # run frontend dev server (react-scripts start) +npm run client:electron # run Electron (`electron .`) from `client/` +``` +- Manual backend run (dev): +```bash +python -m venv .venv +# activate venv +source .venv/bin/activate # macOS / Linux +.\.venv\Scripts\activate # Windows PowerShell +pip install -r server_api/requirements.txt +python server_api/main.py +python server_pytc/main.py +``` +- Tests: +```bash +pytest -q +``` +- Docker backend (build & run): +```bash +docker compose build backend +docker compose up backend +docker compose down +``` + +**Where to Make Changes** +- Frontend UI / components: edit `client/src/` and update `client/package.json` scripts as needed. +- Electron behavior: edit `client/main.js` and electron-specific code. +- API endpoints & backend logic: edit `server_api/` (FastAPI) and `server_pytc/` for worker behavior. +- Models/configs: `pytorch_connectomics/` contains models, configs, and experiment YAMLs. + +**CI / Automation Tips** +- CI jobs can call `npm --prefix client run build` (same as `npm run client:build`) to build frontend from repo root. +- For parallel dev workflows (backend + frontend concurrently), consider adding `concurrently` or `npm-run-all` as dev dependencies and a `dev` script in root. + +**Notes & Recommendations** +- Root `package.json` now includes `client:*` convenience scripts that use `npm --prefix client` to be cross-platform. +- `client/package.json` uses `react-scripts` and defines `build` and `electron` scripts; keep those in sync when modifying frontend tooling. +- Use `start.sh` / `start.bat` for the supported, repository-provided startup flow when possible. + +**Contact / Reference** +- See `README.md` for more detailed setup and video demo link. +- Use `tests/` for examples of expected behavior and for regression checks. + +(Generated by Copilot assistant for developer reference.) \ No newline at end of file diff --git a/DEVLOG.md b/DEVLOG.md new file mode 100644 index 0000000..02ae7d0 --- /dev/null +++ b/DEVLOG.md @@ -0,0 +1,95 @@ +# DEVLOG.md — pytc-client Development Log + +This file records the major development steps, design decisions, and UI/UX changes made to the pytc-client project. It is formatted for easy reading by humans and AI agents. + +--- + +## Project Context +- **Repo:** PytorchConnectomics/pytc-client +- **Frontend:** React + Electron (Ant Design) +- **Backend:** FastAPI (Python), local user management for prototype + +--- + +## Major Features & Changes + +### [2025-11-21] Backend User Management +* **Backend Auth**: Integrated FastAPI with `python-jose` (JWT) and `passlib` (bcrypt) for secure authentication. +* **Database**: Added SQLite database (`sql_app.db`) with SQLAlchemy models for Users. +* **Frontend Integration**: Updated `UserContext` to communicate with backend endpoints (`/register`, `/token`, `/users/me`) instead of local storage. +* **Dependencies**: Added `python-jose`, `passlib`, `sqlalchemy` to requirements. + +### [2025-11-21] Advanced File Management & UI Polish +* **Manual Sign Out**: Added a sign-out button to the header, allowing users to return to the Welcome screen. +* **File Previews**: Implemented a preview modal for files (images and text) triggered by double-click or context menu. +* **Multi-Select & Drag Selection**: Added drag selection box and keyboard shortcuts (Ctrl/Shift) for selecting multiple files. +* **Enhanced Drag & Drop**: Enabled moving multiple selected files/folders at once, including within the same parent directory. +* **Context Menu Enhancements**: Updated context menu to handle multiple selections (bulk Copy/Delete) and hide "Preview" for multi-select. +* **Bug Fixes**: Resolved issues with drag selection (single item) and Electron path handling on Windows. + +### 1. Welcome Page +- Added a full-screen Welcome page as the app's entry point. +- Includes project name, intro, and warm message. +- Two buttons: "Sign in" and "Sign up". +- Styled to resemble cursor.com (modern, clean, gradient background). +- Welcome page is always shown on app start (automatic sign out). + +### 2. Backend User Management (New) +- Replaced local storage with production-ready backend auth. +- Users are stored in `server_api/sql_app.db` (SQLite). +- Passwords are hashed with bcrypt. +- JWT tokens used for session management (stored in localStorage). + +### 3. Main App Navigation +- After login, user sees main app view (tabs: Visualization, Model Training, Model Inference, Tensorboard, Files). +- "Welcome" tab removed from main menu after login. +- Navigation to Welcome page is blocked after login. + +### 4. Files Tab (Google Drive-like) +- Files tab shows three file slots per user. +- Each slot displays file info (name, size, type) or "Empty". +- Upload, rename, and delete actions for each slot (Ant Design components). +- Upload is local only; rename uses modal; delete clears slot. + +### 5. Debugging & Build Process +- Debug banners/messages added and removed for troubleshooting. +- Reminder: Electron app uses static build (`client/build/`), so `npm run build` is required after code changes. +- Hot reload only works in browser dev mode (`npm start`). + +--- + +## Known Issues & Fixes +- [x] Welcome page not showing: fixed by auto sign out on app start. +- [x] "Welcome" tab visible after login: removed from menu. +- [x] Debug messages visible: removed. +- [x] Modals not working in Electron: fixed after proper build/restart. +- [x] Drag selection not selecting single items: fixed. +- [x] Electron "ERR_FILE_NOT_FOUND": fixed path separator in main.js. + +--- + +## Next Steps / TODOs +- [x] Add multi-file support or previews in Files tab. +- [x] Add manual sign-out button in main app view. +- [x] Integrate backend user management (FastAPI, JWT, etc.) for production. +- [ ] Add user profile editing and avatar upload. +- [ ] Improve file upload to support actual file storage (not just metadata). + +--- + +## How to Develop & Test +- Make code changes in `client/src/`. +- Run `npm --prefix client run build` to update the Electron app. +- Start the app with `./start.bat`. +- For live development, use `npm start` (browser only). + +--- + +## AI Agent Notes +- All major UI/UX changes, context, and user flows are documented here. +- Use this file to bootstrap further development, onboarding, or automation. +- For backend integration, see FastAPI endpoints and user model notes above. + +--- + +_Last updated: 2025-11-21_ diff --git a/bash.exe.stackdump b/bash.exe.stackdump new file mode 100644 index 0000000..e7b5590 --- /dev/null +++ b/bash.exe.stackdump @@ -0,0 +1,29 @@ +Stack trace: +Frame Function Args +0007FFFFBBB0 00021006118E (00021028DEE8, 000210272B3E, 0007FFFFBBB0, 0007FFFFAAB0) msys-2.0.dll+0x2118E +0007FFFFBBB0 0002100469BA (000000000000, 000000000000, 000000000000, 0007FFFFBE88) msys-2.0.dll+0x69BA +0007FFFFBBB0 0002100469F2 (00021028DF99, 0007FFFFBA68, 0007FFFFBBB0, 000000000000) msys-2.0.dll+0x69F2 +0007FFFFBBB0 00021006A41E (000000000000, 000000000000, 000000000000, 000000000000) msys-2.0.dll+0x2A41E +0007FFFFBBB0 00021006A545 (0007FFFFBBC0, 000000000000, 000000000000, 000000000000) msys-2.0.dll+0x2A545 +0007FFFFBE90 00021006B9A5 (0007FFFFBBC0, 000000000000, 000000000000, 000000000000) msys-2.0.dll+0x2B9A5 +End of stack trace +Loaded modules: +000100400000 bash.exe +7FFA3F430000 ntdll.dll +7FFA3E6F0000 KERNEL32.DLL +7FFA3CF90000 KERNELBASE.dll +7FFA3EDD0000 USER32.dll +7FFA3D290000 win32u.dll +000210040000 msys-2.0.dll +7FFA3D510000 GDI32.dll +7FFA3CE70000 gdi32full.dll +7FFA3CAC0000 msvcp_win.dll +7FFA3CB60000 ucrtbase.dll +7FFA3D450000 advapi32.dll +7FFA3D8B0000 msvcrt.dll +7FFA3D730000 sechost.dll +7FFA3E4E0000 RPCRT4.dll +7FFA3CE40000 bcrypt.dll +7FFA3C2A0000 CRYPTBASE.DLL +7FFA3D370000 bcryptPrimitives.dll +7FFA3EC80000 IMM32.DLL diff --git a/client/.env b/client/.env index e92841b..9db1133 100644 --- a/client/.env +++ b/client/.env @@ -1,2 +1,4 @@ -REACT_APP_API_PROTOCOL=http -REACT_APP_API_URL=localhost:4242 +SKIP_PREFLIGHT_CHECK=true +REACT_APP_SERVER_PROTOCOL=http +REACT_APP_SERVER_URL=localhost:4242 +PORT=3001 diff --git a/client/main.js b/client/main.js index e3bfae7..a42a4c1 100644 --- a/client/main.js +++ b/client/main.js @@ -7,18 +7,20 @@ require('electron-reload')(__dirname, { let mainWindow -function createWindow () { +function createWindow() { mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: true, - contextIsolation: false + contextIsolation: false, + webSecurity: false, // Allow loading iframes from localhost + allowRunningInsecureContent: true } }) mainWindow.loadURL(url.format({ - pathname: path.join(__dirname, './build/index.html'), + pathname: path.join(__dirname, 'build', 'index.html'), protocol: 'file:', slashes: true })) diff --git a/client/package-lock.json b/client/package-lock.json index dd9df65..cfd80fa 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -30,6 +30,7 @@ }, "devDependencies": { "@babel/plugin-proposal-private-property-in-object": "^7.16.7", + "babel-loader": "8.2.2", "cross-env": "^7.0.3", "electron-reload": "^2.0.0-alpha.1", "prettier": "^3.3.2" @@ -5704,12 +5705,14 @@ } }, "node_modules/babel-loader": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", - "integrity": "sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.2.tgz", + "integrity": "sha512-JvTd0/D889PQBtUXJ2PXaKU/pjZDMtHA9V2ecm+eNRmmBCMR09a+fmpGTNwnJtFmFl5Ei7Vy47LjBb+L0wQ99g==", + "dev": true, + "license": "MIT", "dependencies": { "find-cache-dir": "^3.3.1", - "loader-utils": "^2.0.0", + "loader-utils": "^1.4.0", "make-dir": "^3.1.0", "schema-utils": "^2.6.5" }, @@ -5721,10 +5724,39 @@ "webpack": ">=2" } }, + "node_modules/babel-loader/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/babel-loader/node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/babel-loader/node_modules/schema-utils": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, "dependencies": { "@types/json-schema": "^7.0.5", "ajv": "^6.12.4", @@ -6286,9 +6318,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001643", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz", - "integrity": "sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg==", + "version": "1.0.30001756", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001756.tgz", + "integrity": "sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==", "funding": [ { "type": "opencollective", @@ -6302,7 +6334,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/case-sensitive-paths-webpack-plugin": { "version": "2.4.0", @@ -17360,6 +17393,25 @@ } } }, + "node_modules/react-scripts/node_modules/babel-loader": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.4.1.tgz", + "integrity": "sha512-nXzRChX+Z1GoE6yWavBQg6jDslyFF3SDjl2paADuoQtQW10JqShJt62R6eJQ5m/pjJFDT8xgKIWSP85OY8eXeA==", + "license": "MIT", + "dependencies": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.4", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "engines": { + "node": ">= 8.9" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "webpack": ">=2" + } + }, "node_modules/react-scripts/node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -17384,6 +17436,24 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/react-scripts/node_modules/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/react-scripts/node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", diff --git a/client/package.json b/client/package.json index dbe998a..71742ff 100644 --- a/client/package.json +++ b/client/package.json @@ -52,6 +52,7 @@ }, "devDependencies": { "@babel/plugin-proposal-private-property-in-object": "^7.16.7", + "babel-loader": "8.2.2", "cross-env": "^7.0.3", "electron-reload": "^2.0.0-alpha.1", "prettier": "^3.3.2" @@ -60,6 +61,6 @@ "nth-check": "$nth-check", "resolve-url-loader": "^5.0.0", "svgo": "^3.3.2", - "webpack-dev-server": "^5.2.1" + "webpack-dev-server": "^4.15.0" } -} +} \ No newline at end of file diff --git a/client/src/App.js b/client/src/App.js index 9e605b8..26e308e 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -1,7 +1,9 @@ import { useContext, useEffect, useState } from 'react' import './App.css' import Views from './views/Views' +import Welcome from './views/Welcome' import { AppContext, ContextWrapper } from './contexts/GlobalContext' +import UserContextWrapper, { UserContext } from './contexts/UserContext' import { YamlContextWrapper } from './contexts/YamlContext' function CacheBootstrapper ({ children }) { @@ -30,17 +32,24 @@ function CacheBootstrapper ({ children }) { return children } +function MainContent () { + const { currentUser } = useContext(UserContext) + return currentUser ? : +} + function App () { return ( - - - -
- -
-
-
-
+ + + + +
+ +
+
+
+
+
) } diff --git a/client/src/components/NeuroglancerViewer.js b/client/src/components/NeuroglancerViewer.js new file mode 100644 index 0000000..44d6dd4 --- /dev/null +++ b/client/src/components/NeuroglancerViewer.js @@ -0,0 +1,150 @@ +import React, { useState, useEffect } from 'react'; +import { Button, Spin, Alert, Typography } from 'antd'; +import { ReloadOutlined } from '@ant-design/icons'; +import axios from 'axios'; + +const { Text, Title } = Typography; +const API_BASE = `${process.env.REACT_APP_SERVER_PROTOCOL || 'http'}://${process.env.REACT_APP_SERVER_URL || 'localhost:4243'}`; + +/** + * NeuroglancerViewer Component + * + * Loads and displays Neuroglancer viewer in an iframe using the project's image files. + * Uses the same approach as the Visualization tab. + * + * @param {number} projectId - Project ID to load viewer for + * @param {object} currentSynapse - Current synapse for position reference + */ +function NeuroglancerViewer({ projectId = 1, currentSynapse }) { + const [viewerUrl, setViewerUrl] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + // Load Neuroglancer viewer on mount + useEffect(() => { + loadViewer(); + }, [projectId]); + + const loadViewer = async () => { + setLoading(true); + setError(null); + + try { + const response = await axios.get( + `${API_BASE}/api/synanno/ng-url/${projectId}`, + { withCredentials: true } + ); + + if (response.data.url) { + setViewerUrl(response.data.url); + } else { + // If backend returns a message instead of URL (transition state) + if (response.data.message) { + // We can handle the message here, but for now let's just show the "Setup in Progress" state + // by not setting the URL. + console.log("Neuroglancer message:", response.data.message); + } + setError(null); // Clear error if it's just a transition message + } + } catch (err) { + console.error('Failed to load Neuroglancer viewer', err); + setError(err.response?.data?.detail || 'Failed to load Neuroglancer viewer'); + } finally { + setLoading(false); + } + }; + + const refreshViewer = () => { + loadViewer(); + }; + + if (loading) { + return ( +
+ +
+ ); + } + + if (error) { + return ( +
+ + +
+ ); + } + + // Display viewer in iframe + return ( +
+
+
+ {viewerUrl ? ( +