mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-08-29 04:26:20 +00:00
Fix #769: Resolve all react-hooks/set-state-in-effect lint errors project-wide
This commit is contained in:
@@ -2351,16 +2351,15 @@ export function GraphWorkspace({ externalFocusNodeId, externalFocusToken }: Grap
|
||||
showPluginDock: openDockPanels.length > 0,
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const [prevOpenDockPanels, setPrevOpenDockPanels] = useState(openDockPanels);
|
||||
if (openDockPanels !== prevOpenDockPanels) {
|
||||
setPrevOpenDockPanels(openDockPanels);
|
||||
if (!openDockPanels.length) {
|
||||
setActiveDockPanelId(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!activeDockPanelId || !openDockPanels.some((panel) => panel.id === activeDockPanelId)) {
|
||||
if (activeDockPanelId !== null) setActiveDockPanelId(null);
|
||||
} else if (!activeDockPanelId || !openDockPanels.some((panel) => panel.id === activeDockPanelId)) {
|
||||
setActiveDockPanelId(openDockPanels[0].id);
|
||||
}
|
||||
}, [activeDockPanelId, openDockPanels]);
|
||||
}
|
||||
|
||||
const viewModeItems = useMemo<GraphToolbarItem[]>(() => {
|
||||
if (!hasGraphContent) {
|
||||
|
||||
@@ -369,7 +369,9 @@ export function GraphWorkspaceShell() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const [prevFetchedAt, setPrevFetchedAt] = useState(snapshot?.fetchedAt);
|
||||
if (snapshot?.fetchedAt !== prevFetchedAt) {
|
||||
setPrevFetchedAt(snapshot?.fetchedAt);
|
||||
if (snapshot) {
|
||||
setIsGraphStageReady(false);
|
||||
setActiveNodeCount(null);
|
||||
@@ -383,7 +385,7 @@ export function GraphWorkspaceShell() {
|
||||
stableSamples: 0,
|
||||
});
|
||||
}
|
||||
}, [snapshot?.fetchedAt]);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
@@ -51,12 +51,18 @@ export function LineageDiagram() {
|
||||
document.body.removeChild(anchor);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const [prevActiveId, setPrevActiveId] = useState(activeId);
|
||||
if (activeId !== prevActiveId) {
|
||||
setPrevActiveId(activeId);
|
||||
if (!activeId) {
|
||||
setNodes([]);
|
||||
setEdges([]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
let ignore = false;
|
||||
if (!activeId) return;
|
||||
|
||||
const xLanes = [
|
||||
{ id: "group_agent", type: "group", position: { x: 50, y: 50 }, style: { width: 800, height: 120 } },
|
||||
@@ -89,7 +95,7 @@ export function LineageDiagram() {
|
||||
counters[n.parent_id] = c + 1;
|
||||
return {
|
||||
id: n.id,
|
||||
data: { label: n.label + "\\n(" + n.prov_type + ")" },
|
||||
data: { label: n.label + "\n(" + n.prov_type + ")" },
|
||||
position: { x: 50 + c * 180, y: 30 },
|
||||
parentId: n.parent_id,
|
||||
extent: "parent",
|
||||
@@ -106,13 +112,16 @@ export function LineageDiagram() {
|
||||
style: { stroke: "#58a6ff" }
|
||||
}));
|
||||
|
||||
setNodes([...xLanes, ...mappedNodes]);
|
||||
setEdges(mappedEdges);
|
||||
if (!ignore) {
|
||||
setNodes([...xLanes, ...mappedNodes]);
|
||||
setEdges(mappedEdges);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
fetchLineage();
|
||||
void fetchLineage();
|
||||
return () => { ignore = true; };
|
||||
}, [activeId]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -116,8 +116,51 @@ export function KGOverviewTab() {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
void fetchOverview();
|
||||
}, [fetchOverview]);
|
||||
let ignore = false;
|
||||
async function fetchInitial() {
|
||||
try {
|
||||
const [statsRes, nodesRes] = await Promise.all([
|
||||
fetch("/api/graph/stats"),
|
||||
fetch("/api/graph/nodes?limit=500"),
|
||||
]);
|
||||
|
||||
if (!ignore) {
|
||||
if (statsRes.ok) {
|
||||
const statsData: KGStats = await statsRes.json();
|
||||
setStats(statsData);
|
||||
}
|
||||
|
||||
if (nodesRes.ok) {
|
||||
const nodesData: NodeListResponse = await nodesRes.json();
|
||||
const nodes = nodesData.nodes ?? [];
|
||||
setNodeTypeMap(buildTypeMap(nodes, "type"));
|
||||
|
||||
const edgesRes = await fetch("/api/graph/edges?limit=2000");
|
||||
if (edgesRes.ok) {
|
||||
const edgesData = await edgesRes.json();
|
||||
const edges: { source: string; target: string }[] = edgesData.edges ?? [];
|
||||
const degreeMap: Record<string, number> = {};
|
||||
for (const edge of edges) {
|
||||
degreeMap[edge.source] = (degreeMap[edge.source] ?? 0) + 1;
|
||||
degreeMap[edge.target] = (degreeMap[edge.target] ?? 0) + 1;
|
||||
}
|
||||
const sorted = nodes
|
||||
.map((n) => ({ node: n, neighborCount: degreeMap[n.id] ?? 0 }))
|
||||
.sort((a, b) => b.neighborCount - a.neighborCount)
|
||||
.slice(0, 10);
|
||||
setTopNodes(sorted);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
if (!ignore) setError("Failed to load graph overview. Ensure the server is running.");
|
||||
} finally {
|
||||
if (!ignore) setLoading(false);
|
||||
}
|
||||
}
|
||||
void fetchInitial();
|
||||
return () => { ignore = true; };
|
||||
}, []);
|
||||
|
||||
const nodeTypeEntries = Object.entries(nodeTypeMap).sort((a, b) => b[1] - a[1]);
|
||||
const edgeTypeEntries = stats?.edge_types
|
||||
|
||||
@@ -66,8 +66,27 @@ export function AlignmentsTab() {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
void reload();
|
||||
}, [reload]);
|
||||
let ignore = false;
|
||||
async function fetchInitial() {
|
||||
const [registryResult, alignmentResult] = await Promise.allSettled([
|
||||
loadOntologyRegistry(),
|
||||
loadAlignments(),
|
||||
]);
|
||||
|
||||
if (!ignore) {
|
||||
if (registryResult.status === "fulfilled") {
|
||||
setRegistry(registryResult.value);
|
||||
setSourceOntology((current) => current || registryResult.value[0]?.uri || "");
|
||||
setTargetOntology((current) => current || registryResult.value[1]?.uri || registryResult.value[0]?.uri || "");
|
||||
}
|
||||
if (alignmentResult.status === "fulfilled") {
|
||||
setAlignments(alignmentResult.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
void fetchInitial();
|
||||
return () => { ignore = true; };
|
||||
}, []);
|
||||
|
||||
const relationCounts = useMemo(() => {
|
||||
const counts = new Map<string, number>();
|
||||
|
||||
@@ -29,23 +29,31 @@ export function HealthTab({ onFixInEditor }: HealthTabProps) {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const loadHealth = useCallback(async (uri: string) => {
|
||||
if (!uri) return;
|
||||
setLoading(true);
|
||||
setError("");
|
||||
try {
|
||||
setHealth(await loadOntologyHealth(uri));
|
||||
} catch {
|
||||
// Backend unavailable — show "select an ontology" placeholder, not an error
|
||||
setHealth(null);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
const [prevUri, setPrevUri] = useState(selectedUri);
|
||||
if (selectedUri !== prevUri) {
|
||||
setPrevUri(selectedUri);
|
||||
if (selectedUri) {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
}
|
||||
}, []);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
void loadHealth(selectedUri);
|
||||
}, [selectedUri, loadHealth]);
|
||||
let ignore = false;
|
||||
async function fetchHealth() {
|
||||
if (!selectedUri) return;
|
||||
try {
|
||||
const data = await loadOntologyHealth(selectedUri);
|
||||
if (!ignore) setHealth(data);
|
||||
} catch {
|
||||
if (!ignore) setHealth(null);
|
||||
} finally {
|
||||
if (!ignore) setLoading(false);
|
||||
}
|
||||
}
|
||||
void fetchHealth();
|
||||
return () => { ignore = true; };
|
||||
}, [selectedUri]);
|
||||
|
||||
const exportReport = useCallback(() => {
|
||||
if (!health) return;
|
||||
|
||||
@@ -248,6 +248,15 @@ export function OntologyManager() {
|
||||
const [rightPanel, setRightPanel] = useState<RightPanel>("none");
|
||||
const [actionMsg, setActionMsg] = useState<{ type: "ok" | "err"; text: string } | null>(null);
|
||||
|
||||
const [prevSearchQ, setPrevSearchQ] = useState(searchQ);
|
||||
const [prevStatusFilter, setPrevStatusFilter] = useState(statusFilter);
|
||||
if (searchQ !== prevSearchQ || statusFilter !== prevStatusFilter) {
|
||||
setPrevSearchQ(searchQ);
|
||||
setPrevStatusFilter(statusFilter);
|
||||
setLoading(true);
|
||||
setActionMsg(null);
|
||||
}
|
||||
|
||||
const fetchRegistry = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setActionMsg(null);
|
||||
@@ -268,8 +277,27 @@ export function OntologyManager() {
|
||||
}, [searchQ, statusFilter]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchRegistry();
|
||||
}, [fetchRegistry]);
|
||||
let ignore = false;
|
||||
async function fetchInitial() {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (searchQ) params.set("q", searchQ);
|
||||
const res = await fetch(`/api/ontology/registry?${params}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
if (!ignore) setEntries(data);
|
||||
} else {
|
||||
if (!ignore) setEntries([]);
|
||||
}
|
||||
} catch {
|
||||
if (!ignore) setEntries([]);
|
||||
} finally {
|
||||
if (!ignore) setLoading(false);
|
||||
}
|
||||
}
|
||||
void fetchInitial();
|
||||
return () => { ignore = true; };
|
||||
}, [searchQ, statusFilter]);
|
||||
|
||||
const flashMsg = (type: "ok" | "err", text: string) => {
|
||||
setActionMsg({ type, text });
|
||||
|
||||
@@ -178,17 +178,31 @@ function DetailPanel({
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const [prevUri, setPrevUri] = useState(uri);
|
||||
if (uri !== prevUri) {
|
||||
setPrevUri(uri);
|
||||
setLoading(true);
|
||||
setError("");
|
||||
setDetail(null);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
let ignore = false;
|
||||
fetch(`/api/ontology/entity/${encodeURIComponent(uri)}`)
|
||||
.then((r) => {
|
||||
if (!r.ok) throw new Error("Not found");
|
||||
return r.json();
|
||||
})
|
||||
.then(setDetail)
|
||||
.catch((e) => setError(e.message))
|
||||
.finally(() => setLoading(false));
|
||||
.then((data) => {
|
||||
if (!ignore) setDetail(data);
|
||||
})
|
||||
.catch((e) => {
|
||||
if (!ignore) setError(e.message);
|
||||
})
|
||||
.finally(() => {
|
||||
if (!ignore) setLoading(false);
|
||||
});
|
||||
return () => { ignore = true; };
|
||||
}, [uri]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -40,19 +40,6 @@ export function ProposalReview({ proposalId }: { proposalId: string }) {
|
||||
const [selectedElement, setSelectedElement] = useState<string | null>(null);
|
||||
const [commentText, setCommentText] = useState("");
|
||||
|
||||
const loadProposal = useCallback(async () => {
|
||||
try {
|
||||
const response = await fetch(`/api/ontology/proposals/${proposalId}`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setProposal(data);
|
||||
generateDiff(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load proposal:", error);
|
||||
}
|
||||
}, [proposalId]);
|
||||
|
||||
const generateDiff = useCallback((prop: Proposal) => {
|
||||
const changes: DiffChange[] = [];
|
||||
|
||||
@@ -75,9 +62,38 @@ export function ProposalReview({ proposalId }: { proposalId: string }) {
|
||||
setDiff(changes);
|
||||
}, []);
|
||||
|
||||
const loadProposal = useCallback(async () => {
|
||||
try {
|
||||
const response = await fetch(`/api/ontology/proposals/${proposalId}`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setProposal(data);
|
||||
generateDiff(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load proposal:", error);
|
||||
}
|
||||
}, [proposalId, generateDiff]);
|
||||
|
||||
useEffect(() => {
|
||||
loadProposal();
|
||||
}, [loadProposal]);
|
||||
let ignore = false;
|
||||
async function fetchInitial() {
|
||||
try {
|
||||
const response = await fetch(`/api/ontology/proposals/${proposalId}`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (!ignore) {
|
||||
setProposal(data);
|
||||
generateDiff(data);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load proposal:", error);
|
||||
}
|
||||
}
|
||||
void fetchInitial();
|
||||
return () => { ignore = true; };
|
||||
}, [proposalId, generateDiff]);
|
||||
|
||||
const addComment = useCallback(async () => {
|
||||
if (!selectedElement || !commentText || !proposal) return;
|
||||
|
||||
@@ -77,17 +77,33 @@ function ConceptDetailPanel({
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const [prevUri, setPrevUri] = useState(uri);
|
||||
if (uri !== prevUri) {
|
||||
setPrevUri(uri);
|
||||
setLoading(true);
|
||||
setError("");
|
||||
setDetail(null);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
let ignore = false;
|
||||
fetch(`/api/ontology/skos/concept/${encodeURIComponent(uri)}`)
|
||||
.then((r) => {
|
||||
if (!r.ok) throw new Error("Concept not found");
|
||||
return r.json();
|
||||
})
|
||||
.then(setDetail)
|
||||
.catch((e) => setError(e.message))
|
||||
.finally(() => setLoading(false));
|
||||
.then((data) => {
|
||||
if (!ignore) setDetail(data);
|
||||
})
|
||||
.catch((e) => {
|
||||
if (!ignore) setError(e.message);
|
||||
})
|
||||
.finally(() => {
|
||||
if (!ignore) setLoading(false);
|
||||
});
|
||||
return () => {
|
||||
ignore = true;
|
||||
};
|
||||
}, [uri]);
|
||||
|
||||
const renderUriList = (label: string, uris: string[]) => {
|
||||
@@ -322,16 +338,36 @@ function SchemePanel({
|
||||
}) {
|
||||
const [expanded, setExpanded] = useState(true);
|
||||
const [hierarchy, setHierarchy] = useState<ConceptNode[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loading, setLoading] = useState(expanded);
|
||||
|
||||
const [prevExpanded, setPrevExpanded] = useState(expanded);
|
||||
const [prevSchemeUri, setPrevSchemeUri] = useState(scheme.uri);
|
||||
if (expanded !== prevExpanded || scheme.uri !== prevSchemeUri) {
|
||||
setPrevExpanded(expanded);
|
||||
setPrevSchemeUri(scheme.uri);
|
||||
if (expanded) {
|
||||
setLoading(true);
|
||||
setHierarchy([]);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
let ignore = false;
|
||||
if (!expanded) return;
|
||||
setLoading(true);
|
||||
fetch(`/api/vocabulary/hierarchy?scheme=${encodeURIComponent(scheme.uri)}`)
|
||||
.then((r) => (r.ok ? r.json() : []))
|
||||
.then(setHierarchy)
|
||||
.catch(() => setHierarchy([]))
|
||||
.finally(() => setLoading(false));
|
||||
.then((data) => {
|
||||
if (!ignore) setHierarchy(data);
|
||||
})
|
||||
.catch(() => {
|
||||
if (!ignore) setHierarchy([]);
|
||||
})
|
||||
.finally(() => {
|
||||
if (!ignore) setLoading(false);
|
||||
});
|
||||
return () => {
|
||||
ignore = true;
|
||||
};
|
||||
}, [scheme.uri, expanded]);
|
||||
|
||||
const totalConcepts = countConcepts(hierarchy);
|
||||
@@ -410,7 +446,6 @@ export function SKOSVocabularyManager({ schemeUri }: Props) {
|
||||
const [selectedUri, setSelectedUri] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
fetch("/api/ontology/skos/schemes")
|
||||
.then((r) => (r.ok ? r.json() : []))
|
||||
.then(setSchemes)
|
||||
|
||||
@@ -39,32 +39,42 @@ export function ShaclStudio({ onJumpToNode }: ShaclStudioProps) {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const loadShapes = useCallback(async (uri: string) => {
|
||||
if (!uri) return;
|
||||
setLoading(true);
|
||||
setError("");
|
||||
try {
|
||||
const data = await loadShaclShapes(uri);
|
||||
setShapes(data.shapes);
|
||||
const turtle = data.shacl_turtle;
|
||||
setFullShacl(turtle);
|
||||
setShacl((current) => current || turtle);
|
||||
setSelectedShapeId(null);
|
||||
setValidation(null);
|
||||
} catch {
|
||||
// Shapes not yet generated or backend unavailable — show empty shape list
|
||||
setShapes([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const [prevUri, setPrevUri] = useState(selectedUri);
|
||||
if (selectedUri !== prevUri) {
|
||||
setPrevUri(selectedUri);
|
||||
setShacl("");
|
||||
setFullShacl("");
|
||||
setSelectedShapeId(null);
|
||||
void loadShapes(selectedUri);
|
||||
}, [selectedUri, loadShapes]);
|
||||
setValidation(null);
|
||||
setLoading(true);
|
||||
setError("");
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
let ignore = false;
|
||||
async function fetchShapes() {
|
||||
if (!selectedUri) return;
|
||||
try {
|
||||
const data = await loadShaclShapes(selectedUri);
|
||||
if (!ignore) {
|
||||
setShapes(data.shapes);
|
||||
const turtle = data.shacl_turtle;
|
||||
setFullShacl(turtle);
|
||||
setShacl((current) => current || turtle);
|
||||
setSelectedShapeId(null);
|
||||
setValidation(null);
|
||||
}
|
||||
} catch {
|
||||
if (!ignore) setShapes([]);
|
||||
} finally {
|
||||
if (!ignore) setLoading(false);
|
||||
}
|
||||
}
|
||||
void fetchShapes();
|
||||
return () => {
|
||||
ignore = true;
|
||||
};
|
||||
}, [selectedUri]);
|
||||
|
||||
const handleGenerate = useCallback(async () => {
|
||||
if (!selectedUri) return;
|
||||
|
||||
@@ -74,9 +74,34 @@ export function VersionsTab() {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
loadVersions();
|
||||
loadProposals();
|
||||
}, [loadVersions, loadProposals]);
|
||||
let ignore = false;
|
||||
async function fetchInitial() {
|
||||
try {
|
||||
const [propRes] = await Promise.all([
|
||||
fetch("/api/ontology/proposals"),
|
||||
]);
|
||||
if (propRes.ok) {
|
||||
const propData = await propRes.json();
|
||||
if (!ignore) setProposals(propData);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to load proposals", e);
|
||||
}
|
||||
|
||||
if (!ontologyUri) return;
|
||||
try {
|
||||
const verRes = await fetch(`/api/ontology/versions/${encodeURIComponent(ontologyUri)}`);
|
||||
if (verRes.ok) {
|
||||
const verData = await verRes.json();
|
||||
if (!ignore) setVersions(verData);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to load versions", e);
|
||||
}
|
||||
}
|
||||
void fetchInitial();
|
||||
return () => { ignore = true; };
|
||||
}, [ontologyUri]);
|
||||
|
||||
const approveProposal = useCallback(async (proposalId: string) => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user