diff --git a/client/main.js b/client/main.js
index 203001a..d80fb2e 100644
--- a/client/main.js
+++ b/client/main.js
@@ -70,90 +70,8 @@ function createMenu() {
],
},
{
- label: "Views",
- submenu: [
- {
- label: "Change Views",
- click: () => {
- if (mainWindow) {
- mainWindow.webContents.send("change-views");
- }
- },
- },
- { type: "separator" },
- {
- label: "File Management",
- type: "checkbox",
- checked: true,
- enabled: false, // Always on
- },
- {
- label: "Visualization",
- type: "checkbox",
- checked: false,
- click: (menuItem) =>
- mainWindow.webContents.send(
- "toggle-tab",
- "visualization",
- menuItem.checked,
- ),
- },
- {
- label: "Model Training",
- type: "checkbox",
- checked: false,
- click: (menuItem) =>
- mainWindow.webContents.send(
- "toggle-tab",
- "training",
- menuItem.checked,
- ),
- },
- {
- label: "Model Inference",
- type: "checkbox",
- checked: false,
- click: (menuItem) =>
- mainWindow.webContents.send(
- "toggle-tab",
- "inference",
- menuItem.checked,
- ),
- },
- {
- label: "Tensorboard",
- type: "checkbox",
- checked: false,
- click: (menuItem) =>
- mainWindow.webContents.send(
- "toggle-tab",
- "monitoring",
- menuItem.checked,
- ),
- },
- {
- label: "SynAnno",
- type: "checkbox",
- checked: false,
- click: (menuItem) =>
- mainWindow.webContents.send(
- "toggle-tab",
- "synanno",
- menuItem.checked,
- ),
- },
- {
- label: "Mask Proofreading",
- type: "checkbox",
- checked: false,
- click: (menuItem) =>
- mainWindow.webContents.send(
- "toggle-tab",
- "mask-proofreading",
- menuItem.checked,
- ),
- },
- ],
+ label: "Window",
+ submenu: [{ role: "minimize" }, { role: "close" }],
},
];
diff --git a/client/preload.js b/client/preload.js
index 14e18fe..9c18439 100644
--- a/client/preload.js
+++ b/client/preload.js
@@ -1,25 +1,8 @@
const { contextBridge, ipcRenderer } = require("electron");
-function subscribe(channel) {
- return (listener) => {
- if (typeof listener !== "function") {
- return () => {};
- }
-
- const wrappedListener = (_event, ...args) => listener(...args);
- ipcRenderer.on(channel, wrappedListener);
-
- return () => {
- ipcRenderer.removeListener(channel, wrappedListener);
- };
- };
-}
-
contextBridge.exposeInMainWorld("electronAPI", {
isElectron: true,
openLocalFile: (options = {}) => ipcRenderer.invoke("open-local-file", options),
revealInFinder: (targetPath) =>
ipcRenderer.invoke("reveal-in-finder", targetPath),
- onToggleTab: subscribe("toggle-tab"),
- onChangeViews: subscribe("change-views"),
});
diff --git a/client/src/components/NeuroglancerViewer.js b/client/src/components/NeuroglancerViewer.js
deleted file mode 100644
index 2e791d8..0000000
--- a/client/src/components/NeuroglancerViewer.js
+++ /dev/null
@@ -1,157 +0,0 @@
-import React, { useState, useEffect } from "react";
-import { Button, Spin, Alert, Typography } from "antd";
-import { ReloadOutlined } from "@ant-design/icons";
-import { apiClient } from "../api";
-
-const { Text, Title } = Typography;
-
-/**
- * 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 apiClient.get(`/api/synanno/ng-url/${projectId}`);
-
- 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 (
-
-
- }
- onClick={refreshViewer}
- title="Refresh viewer"
- />
- {currentSynapse && (
-
- Synapse #{currentSynapse.id}
-
- )}
-
- {viewerUrl ? (
-
- ) : (
-
-
Setup in Progress
-
Data server is running. Preparing viewer...
-
-
- )}
-
- );
-}
-
-export default NeuroglancerViewer;
diff --git a/client/src/components/ProofreadingControls.js b/client/src/components/ProofreadingControls.js
deleted file mode 100644
index 05a6e78..0000000
--- a/client/src/components/ProofreadingControls.js
+++ /dev/null
@@ -1,181 +0,0 @@
-import React, { useState, useEffect } from "react";
-import { Button, Input, Space, Typography, Divider } from "antd";
-import {
- CheckOutlined,
- CloseOutlined,
- QuestionOutlined,
- ArrowRightOutlined,
-} from "@ant-design/icons";
-
-const { Text } = Typography;
-
-/**
- * ProofreadingControls Component
- *
- * Provides UI controls for classifying synapses and editing neuron IDs.
- * Includes status buttons, input fields, and save/navigation buttons.
- *
- * @param {object} currentSynapse - Currently selected synapse
- * @param {function} onSave - Callback to save changes
- * @param {function} onNext - Callback to navigate to next synapse
- */
-function ProofreadingControls({ currentSynapse, onSave, onNext }) {
- const [status, setStatus] = useState("error");
- const [preNeuronId, setPreNeuronId] = useState("");
- const [postNeuronId, setPostNeuronId] = useState("");
-
- // Update local state when current synapse changes
- useEffect(() => {
- if (currentSynapse) {
- setStatus(currentSynapse.status);
- setPreNeuronId(currentSynapse.pre_neuron_id || "");
- setPostNeuronId(currentSynapse.post_neuron_id || "");
- }
- }, [currentSynapse]);
-
- const handleSave = async () => {
- const updates = {
- status,
- pre_neuron_id: preNeuronId ? parseInt(preNeuronId) : null,
- post_neuron_id: postNeuronId ? parseInt(postNeuronId) : null,
- };
-
- await onSave(updates);
- };
-
- const handleSaveAndNext = async () => {
- await handleSave();
- onNext();
- };
-
- if (!currentSynapse) {
- return (
-
- No synapse selected
-
- );
- }
-
- return (
-
- {/* Current Synapse Info */}
-
-
- Synapse #{currentSynapse.id}
-
-
-
- Position: ({currentSynapse.x.toFixed(1)},{" "}
- {currentSynapse.y.toFixed(1)}, {currentSynapse.z.toFixed(1)})
-
- {currentSynapse.confidence && (
-
- Confidence: {(currentSynapse.confidence * 100).toFixed(0)}%
-
- )}
-
-
-
-
-
- {/* Status Classification */}
-
-
- Status Classification
-
-
- }
- onClick={() => setStatus("correct")}
- style={{
- backgroundColor: status === "correct" ? "#52c41a" : undefined,
- borderColor: status === "correct" ? "#52c41a" : undefined,
- }}
- >
- Correct (C)
-
- }
- onClick={() => setStatus("incorrect")}
- >
- Incorrect (X)
-
- }
- onClick={() => setStatus("unsure")}
- style={{
- backgroundColor: status === "unsure" ? "#faad14" : undefined,
- borderColor: status === "unsure" ? "#faad14" : undefined,
- color: status === "unsure" ? "#fff" : undefined,
- }}
- >
- Unsure (U)
-
-
-
-
-
-
- {/* Neuron ID Inputs */}
-
-
- Pre-synaptic Neuron ID
-
- setPreNeuronId(e.target.value)}
- placeholder="Enter neuron ID"
- type="number"
- />
-
-
-
-
- Post-synaptic Neuron ID
-
- setPostNeuronId(e.target.value)}
- placeholder="Enter neuron ID"
- type="number"
- />
-
-
-
-
- {/* Action Buttons */}
-
-
- }
- onClick={handleSaveAndNext}
- >
- Save & Next (→)
-
-
-
- );
-}
-
-export default ProofreadingControls;
diff --git a/client/src/components/SynapseList.js b/client/src/components/SynapseList.js
deleted file mode 100644
index 784d667..0000000
--- a/client/src/components/SynapseList.js
+++ /dev/null
@@ -1,142 +0,0 @@
-import React from "react";
-import { List, Typography, Progress } from "antd";
-import {
- CheckCircleOutlined,
- CloseCircleOutlined,
- QuestionCircleOutlined,
-} from "@ant-design/icons";
-
-const { Text } = Typography;
-
-/**
- * SynapseList Component
- *
- * Displays a scrollable list of synapses with status indicators and progress tracking.
- * Highlights the currently selected synapse and allows clicking to navigate.
- *
- * @param {array} synapses - Array of synapse objects
- * @param {number} currentIndex - Index of currently selected synapse
- * @param {function} onSelectSynapse - Callback when synapse is clicked
- * @param {number} reviewedCount - Number of reviewed synapses
- */
-function SynapseList({
- synapses,
- currentIndex,
- onSelectSynapse,
- reviewedCount,
-}) {
- /**
- * Get icon based on synapse status
- */
- const getStatusIcon = (status) => {
- switch (status) {
- case "correct":
- return ;
- case "incorrect":
- return ;
- case "unsure":
- return ;
- default:
- return null; // No icon for 'error' status
- }
- };
-
- // Calculate progress
- const totalErrors = synapses.filter((s) => s.status === "error").length;
- const progress = totalErrors > 0 ? (reviewedCount / totalErrors) * 100 : 0;
-
- return (
-
- {/* Progress Section */}
-
-
- Progress
-
-
-
- {reviewedCount} / {totalErrors} reviewed
-
-
-
- {/* Synapse List */}
-
(
- onSelectSynapse(index)}
- style={{
- cursor: "pointer",
- backgroundColor:
- index === currentIndex ? "#e6f7ff" : "transparent",
- borderLeft:
- index === currentIndex
- ? "3px solid #1890ff"
- : "3px solid transparent",
- padding: "8px 12px",
- transition: "all 0.2s",
- }}
- onMouseEnter={(e) => {
- if (index !== currentIndex) {
- e.currentTarget.style.backgroundColor = "#f5f5f5";
- }
- }}
- onMouseLeave={(e) => {
- if (index !== currentIndex) {
- e.currentTarget.style.backgroundColor = "transparent";
- }
- }}
- >
-
-
-
- Synapse #{synapse.id}
-
- {getStatusIcon(synapse.status)}
-
-
- ({synapse.x.toFixed(1)}, {synapse.y.toFixed(1)},{" "}
- {synapse.z.toFixed(1)})
-
- {synapse.confidence && (
-
- Confidence: {(synapse.confidence * 100).toFixed(0)}%
-
- )}
-
-
- )}
- />
-
- );
-}
-
-export default SynapseList;
diff --git a/client/src/components/WorkflowSelector.js b/client/src/components/WorkflowSelector.js
deleted file mode 100644
index d5c4492..0000000
--- a/client/src/components/WorkflowSelector.js
+++ /dev/null
@@ -1,112 +0,0 @@
-import React, { useState } from "react";
-import { Modal, Button, Checkbox, Space, Typography, Row, Col } from "antd";
-import {
- FolderOpenOutlined,
- BugOutlined,
- EyeOutlined,
- ExperimentOutlined,
- ThunderboltOutlined,
- DashboardOutlined,
- ApartmentOutlined,
-} from "@ant-design/icons";
-
-const { Title, Text } = Typography;
-
-const WorkflowSelector = ({ visible, onSelect, onCancel }) => {
- // Default to having Files selected
- const [selectedModes, setSelectedModes] = useState(["files"]);
-
- const handleOk = () => {
- onSelect(selectedModes);
- };
-
- const options = [
- { label: "File Management", value: "files", icon: },
- { label: "Visualization", value: "visualization", icon: },
- {
- label: "Model Training",
- value: "training",
- icon: ,
- },
- {
- label: "Model Inference",
- value: "inference",
- icon: ,
- },
- { label: "Tensorboard", value: "monitoring", icon: },
- { label: "SynAnno", value: "synanno", icon: },
- {
- label: "Mask Proofreading",
- value: "mask-proofreading",
- icon: ,
- },
- ];
-
- const onChange = (checkedValues) => {
- setSelectedModes(checkedValues);
- };
-
- return (
- Change Views}
- open={visible}
- onOk={handleOk}
- onCancel={onCancel}
- footer={[
- ,
- ]}
- centered
- width={600}
- >
-
- Select the workflows you want to use.
-
-
-
-
- {options.map((opt) => (
-
-
-
-
- {opt.icon}
- {opt.label}
-
-
-
-
- ))}
-
-
-
- );
-};
-
-export default WorkflowSelector;
diff --git a/client/src/electronApi.js b/client/src/electronApi.js
index 6105010..172c307 100644
--- a/client/src/electronApi.js
+++ b/client/src/electronApi.js
@@ -6,10 +6,6 @@ function getElectronAPI() {
return window.electronAPI || null;
}
-export function isElectronAvailable() {
- return Boolean(getElectronAPI()?.isElectron);
-}
-
export function openLocalFile(options = {}) {
const api = getElectronAPI();
if (!api) {
@@ -25,19 +21,3 @@ export function revealInFinder(targetPath) {
}
return api.revealInFinder(targetPath);
}
-
-export function onToggleTab(listener) {
- const api = getElectronAPI();
- if (!api) {
- return () => {};
- }
- return api.onToggleTab(listener);
-}
-
-export function onChangeViews(listener) {
- const api = getElectronAPI();
- if (!api) {
- return () => {};
- }
- return api.onChangeViews(listener);
-}
diff --git a/client/src/views/ProofReading.js b/client/src/views/ProofReading.js
deleted file mode 100644
index 5fe1c98..0000000
--- a/client/src/views/ProofReading.js
+++ /dev/null
@@ -1,273 +0,0 @@
-import React, { useState, useEffect } from "react";
-import { Layout, message, Spin, Empty } from "antd";
-import { apiClient } from "../api";
-import NeuroglancerViewer from "../components/NeuroglancerViewer";
-import SynapseList from "../components/SynapseList";
-import ProofreadingControls from "../components/ProofreadingControls";
-
-const { Sider, Content } = Layout;
-
-/**
- * ProofReading Component
- *
- * Main view for synapse proofreading. Integrates Neuroglancer viewer,
- * synapse list, and proofreading controls. Supports keyboard shortcuts
- * for efficient workflow.
- */
-function ProofReading() {
- const [synapses, setSynapses] = useState([]);
- const [currentIndex, setCurrentIndex] = useState(0);
- const [neuroglancerUrl, setNeuroglancerUrl] = useState("");
- const [loading, setLoading] = useState(true);
- const [projectId, setProjectId] = useState(1); // Default to first project
- const [reviewedCount, setReviewedCount] = useState(0);
-
- // Fetch synapses on mount
- useEffect(() => {
- fetchSynapses();
- fetchNeuroglancerUrl();
- }, [projectId]);
-
- // Keyboard shortcuts
- useEffect(() => {
- const handleKeyPress = (e) => {
- // Don't trigger shortcuts when typing in input fields
- const target = e.target;
- if (
- target &&
- (target.tagName === "INPUT" ||
- target.tagName === "TEXTAREA" ||
- target.isContentEditable)
- )
- return;
-
- switch (e.key.toLowerCase()) {
- case "c":
- updateStatus("correct");
- break;
- case "x":
- updateStatus("incorrect");
- break;
- case "u":
- updateStatus("unsure");
- break;
- case "arrowright":
- goToNext();
- break;
- case "arrowleft":
- goToPrevious();
- break;
- case "s":
- saveCurrent();
- break;
- default:
- break;
- }
- };
-
- window.addEventListener("keydown", handleKeyPress);
- return () => window.removeEventListener("keydown", handleKeyPress);
- }, [currentIndex, synapses]);
-
- /**
- * Fetch synapses from backend
- */
- const fetchSynapses = async () => {
- try {
- setLoading(true);
- const res = await apiClient.get(`/api/projects/${projectId}/synapses`);
- setSynapses(res.data);
-
- // Count reviewed synapses (not in error state)
- const reviewed = res.data.filter((s) => s.status !== "error").length;
- setReviewedCount(reviewed);
-
- setLoading(false);
- } catch (err) {
- console.error("Failed to fetch synapses", err);
- message.error("Failed to load synapses");
- setLoading(false);
- }
- };
-
- /**
- * Fetch Neuroglancer URL for the project
- */
- const fetchNeuroglancerUrl = async () => {
- try {
- const res = await apiClient.get(`/api/synanno/ng-url/${projectId}`);
- setNeuroglancerUrl(res.data.url);
- } catch (err) {
- console.error("Failed to fetch Neuroglancer URL", err);
- message.error("Failed to load Neuroglancer viewer");
- }
- };
-
- /**
- * Update status of current synapse locally
- */
- const updateStatus = (status) => {
- if (synapses[currentIndex]) {
- const updated = [...synapses];
- updated[currentIndex] = { ...updated[currentIndex], status };
- setSynapses(updated);
- }
- };
-
- /**
- * Save current synapse to backend
- */
- const saveCurrent = async () => {
- if (!synapses[currentIndex]) return;
-
- try {
- await apiClient.put(`/api/synapses/${synapses[currentIndex].id}`, {
- status: synapses[currentIndex].status,
- pre_neuron_id: synapses[currentIndex].pre_neuron_id,
- post_neuron_id: synapses[currentIndex].post_neuron_id,
- });
- message.success("Synapse updated");
-
- // Update reviewed count
- const reviewed = synapses.filter((s) => s.status !== "error").length;
- setReviewedCount(reviewed);
- } catch (err) {
- console.error("Failed to update synapse", err);
- message.error("Failed to save changes");
- }
- };
-
- /**
- * Save synapse with provided updates
- */
- const handleSave = async (updates) => {
- if (!synapses[currentIndex]) return;
-
- try {
- await apiClient.put(
- `/api/synapses/${synapses[currentIndex].id}`,
- updates,
- );
-
- // Update local state
- const updated = [...synapses];
- updated[currentIndex] = { ...updated[currentIndex], ...updates };
- setSynapses(updated);
-
- message.success("Synapse updated");
-
- // Update reviewed count
- const reviewed = updated.filter((s) => s.status !== "error").length;
- setReviewedCount(reviewed);
- } catch (err) {
- console.error("Failed to update synapse", err);
- message.error("Failed to save changes");
- }
- };
-
- /**
- * Navigate to next synapse
- */
- const goToNext = () => {
- if (currentIndex < synapses.length - 1) {
- setCurrentIndex(currentIndex + 1);
- } else {
- message.info("You have reached the last synapse");
- }
- };
-
- /**
- * Navigate to previous synapse
- */
- const goToPrevious = () => {
- if (currentIndex > 0) {
- setCurrentIndex(currentIndex - 1);
- } else {
- message.info("You are at the first synapse");
- }
- };
-
- // Loading state
- if (loading) {
- return (
-
-
-
- );
- }
-
- // Empty state
- if (synapses.length === 0) {
- return (
-
-
-
- );
- }
-
- return (
-
- {/* Left Panel: Synapse List */}
-
-
-
-
- {/* Center: Neuroglancer Viewer */}
-
-
-
-
- {/* Right Panel: Proofreading Controls */}
-
-
-
-
- );
-}
-
-export default ProofReading;
diff --git a/client/src/views/Views.js b/client/src/views/Views.js
index 6412b7c..e442adf 100644
--- a/client/src/views/Views.js
+++ b/client/src/views/Views.js
@@ -7,7 +7,6 @@ import {
ThunderboltOutlined,
DashboardOutlined,
BugOutlined,
- ApartmentOutlined,
MessageOutlined,
} from "@ant-design/icons";
import FilesManager from "./FilesManager";
@@ -15,119 +14,43 @@ import Visualization from "./Visualization";
import ModelTraining from "./ModelTraining";
import ModelInference from "./ModelInference";
import Monitoring from "./Monitoring";
-import ProofReading from "./ProofReading";
import MaskProofreading from "./MaskProofreading";
-import WorkflowSelector from "../components/WorkflowSelector";
import Chatbot from "../components/Chatbot";
-import {
- isElectronAvailable,
- onChangeViews,
- onToggleTab,
-} from "../electronApi";
const { Content } = Layout;
+const MODULE_ITEMS = [
+ { label: "File Management", key: "files", icon: },
+ { label: "Visualization", key: "visualization", icon: },
+ { label: "Model Training", key: "training", icon: },
+ {
+ label: "Model Inference",
+ key: "inference",
+ icon: ,
+ },
+ { label: "Tensorboard", key: "monitoring", icon: },
+ {
+ label: "Mask Proofreading",
+ key: "mask-proofreading",
+ icon: ,
+ },
+];
+
function Views() {
- // State
const [current, setCurrent] = useState("files");
- const [visibleTabs, setVisibleTabs] = useState(new Set(["files"]));
const [visitedTabs, setVisitedTabs] = useState(new Set(["files"]));
- const [workflowModalVisible, setWorkflowModalVisible] = useState(true);
const [isChatOpen, setIsChatOpen] = useState(false);
const [chatWidth, setChatWidth] = useState(560);
const isResizing = useRef(false);
- // Lifted state from Workspace
const [viewers, setViewers] = useState([]);
const [isInferring, setIsInferring] = useState(false);
- const allItems = [
- { label: "File Management", key: "files", icon: },
- { label: "Visualization", key: "visualization", icon: },
- { label: "Model Training", key: "training", icon: },
- {
- label: "Model Inference",
- key: "inference",
- icon: ,
- },
- { label: "Tensorboard", key: "monitoring", icon: },
- { label: "SynAnno", key: "synanno", icon: },
- {
- label: "Mask Proofreading",
- key: "mask-proofreading",
- icon: ,
- },
- ];
-
- const items = allItems.filter((item) => visibleTabs.has(item.key));
-
const onClick = (e) => {
setCurrent(e.key);
setVisitedTabs((prev) => new Set(prev).add(e.key));
};
- // Helper to activate tabs and set valid current
- const applyModes = (modes) => {
- const modeList = Array.isArray(modes) ? modes : [modes];
- if (modeList.length === 0) return;
-
- setVisitedTabs((prev) => {
- const next = new Set(prev);
- modeList.forEach((m) => next.add(m));
- return next;
- });
- setVisibleTabs((prev) => {
- const next = new Set(prev);
- modeList.forEach((m) => next.add(m));
- return next;
- });
-
- // Switch to first selected tab, or 'files' if included, or keep current if valid
- if (modeList.includes("files")) {
- setCurrent("files");
- } else {
- setCurrent(modeList[0]);
- }
- };
-
- const handleWorkflowSelect = (modes) => {
- setWorkflowModalVisible(false);
- applyModes(modes);
- };
-
- // IPC Listener
- useEffect(() => {
- if (!isElectronAvailable()) {
- return;
- }
-
- const handleToggleTab = (key, checked) => {
- setVisibleTabs((prev) => {
- const newSet = new Set(prev);
- if (checked) {
- newSet.add(key);
- } else {
- newSet.delete(key);
- if (current === key) setCurrent("files");
- }
- return newSet;
- });
- };
-
- const handleChangeViews = () => {
- // User clicked "Change Views"
- setWorkflowModalVisible(true);
- };
-
- const removeToggleTabListener = onToggleTab(handleToggleTab);
- const removeChangeViewsListener = onChangeViews(handleChangeViews);
-
- return () => {
- removeToggleTabListener();
- removeChangeViewsListener();
- };
- }, [current]);
-
const startResizing = useCallback((e) => {
isResizing.current = true;
e.preventDefault();
@@ -168,14 +91,6 @@ function Views() {
return (
- {
- setWorkflowModalVisible(false);
- }}
- />
-
,
)}
- {renderTabContent("synanno", )}
{renderTabContent("mask-proofreading", )}
{
+ const React = require("react");
+
+ const Layout = ({ children }) => {children}
;
+ Layout.Content = ({ children }) => {children}
;
+
+ const Menu = ({ items = [], onClick }) => (
+
+ );
+
+ return {
+ Layout,
+ Menu,
+ Button: ({ children, ...props }) => ,
+ Drawer: ({ children, open }) => (open ? {children}
: null),
+ };
+});
+
+jest.mock("@ant-design/icons", () => {
+ const Icon = () => ;
+ return {
+ FolderOpenOutlined: Icon,
+ EyeOutlined: Icon,
+ ExperimentOutlined: Icon,
+ ThunderboltOutlined: Icon,
+ DashboardOutlined: Icon,
+ BugOutlined: Icon,
+ MessageOutlined: Icon,
+ };
+});
+
+jest.mock("./FilesManager", () => () => Files Manager Content
);
+jest.mock("./Visualization", () => () => Visualization Content
);
+jest.mock("./ModelTraining", () => () => Training Content
);
+jest.mock("./ModelInference", () => () => Inference Content
);
+jest.mock("./Monitoring", () => () => Monitoring Content
);
+jest.mock("./MaskProofreading", () => () => Mask Proofreading Content
);
+jest.mock("../components/Chatbot", () => () => Chatbot
);
+
+describe("Views", () => {
+ it("shows all active modules without the workflow selector", () => {
+ render();
+
+ expect(screen.getByText("File Management")).toBeTruthy();
+ expect(screen.getByText("Visualization")).toBeTruthy();
+ expect(screen.getByText("Model Training")).toBeTruthy();
+ expect(screen.getByText("Model Inference")).toBeTruthy();
+ expect(screen.getByText("Tensorboard")).toBeTruthy();
+ expect(screen.getByText("Mask Proofreading")).toBeTruthy();
+ expect(screen.queryByText("SynAnno")).toBeNull();
+ expect(screen.queryByText("Change Views")).toBeNull();
+ expect(screen.queryByText("Launch Selected")).toBeNull();
+ expect(screen.getByText("Files Manager Content")).toBeTruthy();
+ });
+
+ it("lazy loads additional tab content after selection", () => {
+ render();
+
+ expect(screen.queryByText("Visualization Content")).toBeNull();
+
+ fireEvent.click(screen.getByText("Visualization"));
+
+ expect(screen.getByText("Visualization Content")).toBeTruthy();
+ });
+});
diff --git a/server_api/chatbot/file_summaries/GettingStarted.md b/server_api/chatbot/file_summaries/GettingStarted.md
index d215ce5..56276e1 100644
--- a/server_api/chatbot/file_summaries/GettingStarted.md
+++ b/server_api/chatbot/file_summaries/GettingStarted.md
@@ -1,26 +1,23 @@
# Getting Started with PyTC Client
-PyTC Client (PyTorch Connectomics Client) is a desktop application for biomedical image segmentation. It provides tools for managing files, visualizing data in Neuroglancer, training and running inference with deep learning models, proofreading synapse annotations, and detecting errors in image stacks.
+PyTC Client (PyTorch Connectomics Client) is a desktop application for biomedical image segmentation. It provides tools for managing files, visualizing data in Neuroglancer, training and running inference with deep learning models, monitoring experiments, and reviewing image masks.
## Launching the Application
-When you first open PyTC Client, a **Change Views** dialog appears. This lets you choose which workflow tabs to enable. The available workflows are:
+When you first open PyTC Client, the main workflows are available immediately in the top navigation bar:
- **File Management** — Browse, upload, and organize files on the server
- **Visualization** — View image and label data in Neuroglancer
- **Model Training** — Configure and launch training jobs
- **Model Inference** — Run inference with trained models
- **Tensorboard** — Monitor training metrics in real time
-- **SynAnno** — Proofread synapse annotations
-- **Worm Error Handling** — Detect and classify errors in worm image stacks
-
-Check the workflows you want, then click **Launch Selected**. You can change your selection later by clicking **Change Views** in the top navigation bar.
+- **Mask Proofreading** — Review and edit mask layers in image stacks
## Application Layout
The application has three main areas:
-1. **Top Navigation Bar** — Displays the PyTC logo, application title, and a row of tabs for each enabled workflow. Click a tab to switch between pages. The bar also includes a **Change Views** button and an **AI Chat** toggle button.
+1. **Top Navigation Bar** — Displays the PyTC logo, application title, and a row of tabs for each workflow. Click a tab to switch between pages. The bar also includes an **AI Chat** toggle button.
2. **Main Content Area** — Shows the currently selected workflow page (e.g., File Manager, Visualization, Model Training).
diff --git a/server_api/chatbot/file_summaries/Proofreading.md b/server_api/chatbot/file_summaries/Proofreading.md
deleted file mode 100644
index a970404..0000000
--- a/server_api/chatbot/file_summaries/Proofreading.md
+++ /dev/null
@@ -1,59 +0,0 @@
-# SynAnno Proofreading Page
-
-The SynAnno (Synapse Annotation) proofreading page lets you review and classify predicted synapse detections. It displays a Neuroglancer 3D viewer alongside a list of synapses and classification controls, enabling efficient annotation workflows.
-
-## Layout
-
-The proofreading page is divided into three panels:
-
-1. **Synapse List (left panel)** — A scrollable list of all synapses in the current project. Each entry shows:
- - **Synapse ID** (e.g., "Synapse #1")
- - **Position** coordinates (x, y, z)
- - **Confidence** score (percentage)
- - **Status icon** — A colored icon indicating the current classification:
- - Green checkmark = Correct
- - Red X = Incorrect
- - Yellow question mark = Unsure
- - No icon = Unreviewed
- - A **progress bar** at the top showing how many synapses have been reviewed out of the total.
-
-2. **Neuroglancer Viewer (center panel)** — A 3D viewer displaying the image volume. When you select a synapse, the viewer centers on that synapse's location. A **Refresh** button appears in the top-right corner to reload the viewer. The current synapse ID is displayed next to the refresh button.
-
-3. **Proofreading Controls (right panel)** — Controls for classifying the selected synapse and editing metadata.
-
-## Reviewing Synapses
-
-1. Click on any synapse in the **Synapse List** to select it. The list highlights the selected synapse with a blue background and left border. The Neuroglancer viewer navigates to that synapse's 3D position.
-
-2. In the **Proofreading Controls** panel, you will see:
- - **Synapse info** — The synapse ID, position, and confidence score.
- - **Status Classification** buttons:
- - **Correct (C)** — Green button. Mark the synapse as a true positive.
- - **Incorrect (X)** — Red button. Mark the synapse as a false positive.
- - **Unsure (U)** — Yellow button. Mark the synapse as uncertain.
- - **Pre-synaptic Neuron ID** — A text input to enter or edit the pre-synaptic neuron ID number.
- - **Post-synaptic Neuron ID** — A text input to enter or edit the post-synaptic neuron ID number.
-
-3. After setting the classification and optionally entering neuron IDs, save your work:
- - Click **Save (S)** to save the current synapse's classification and neuron IDs.
- - Click **Save & Next (→)** to save and automatically advance to the next synapse in the list.
-
-## Keyboard Shortcuts
-
-These shortcuts work when the proofreading page is active and you are not typing in an input field:
-
-| Shortcut | Action |
-| --------------- | --------------------------------- |
-| C | Mark current synapse as Correct |
-| X | Mark current synapse as Incorrect |
-| U | Mark current synapse as Unsure |
-| S | Save current synapse |
-| Arrow Right (→) | Move to the next synapse |
-| Arrow Left (←) | Move to the previous synapse |
-
-## Workflow Tips
-
-- Use **Save & Next** (or press **S** then **→**) for rapid sequential review.
-- The progress bar at the top of the synapse list helps you track how many synapses you have reviewed.
-- You can click any synapse in the list at any time to jump to it — you do not have to review them in order.
-- Neuron ID fields are optional and can be filled in during a second pass.
diff --git a/server_api/main.py b/server_api/main.py
index d93d767..a97153f 100644
--- a/server_api/main.py
+++ b/server_api/main.py
@@ -20,7 +20,6 @@
from server_api.auth import models, database, router as auth_router
from server_api.auth.database import get_db
from server_api.auth.router import get_current_user
-from server_api.synanno import router as synanno_router
from server_api.ehtool import router as ehtool_router
from fastapi.staticfiles import StaticFiles
@@ -77,7 +76,6 @@ def _ensure_chatbot():
app.mount("/uploads", StaticFiles(directory="uploads"), name="uploads")
app.include_router(auth_router.router)
-app.include_router(synanno_router.router, tags=["synanno"])
app.include_router(ehtool_router.router, prefix="/eh", tags=["ehtool"])
app.add_middleware(
diff --git a/tests/test_pytc_runtime_routes.py b/tests/test_pytc_runtime_routes.py
index 9c8cf54..63e6ddf 100644
--- a/tests/test_pytc_runtime_routes.py
+++ b/tests/test_pytc_runtime_routes.py
@@ -66,6 +66,11 @@ class ServerApiProxyTests(unittest.TestCase):
def setUp(self):
self.client = TestClient(server_api_app)
+ def test_synanno_route_is_not_mounted(self):
+ response = self.client.get("/api/synanno/ng-url/1")
+
+ self.assertEqual(response.status_code, 404)
+
def test_inference_status_proxy_returns_worker_payload(self):
payload = {"isRunning": False, "pid": None, "exitCode": 0}
with patch(