highlight toggle added

This commit is contained in:
abhigyanpatwari 2026-01-08 05:05:15 +05:30
parent 0ea6768c51
commit 9d219b7e9b
2 changed files with 79 additions and 24 deletions

View file

@ -1,5 +1,5 @@
import { useEffect, useCallback, useState, forwardRef, useImperativeHandle } from 'react';
import { ZoomIn, ZoomOut, Maximize2, Focus, RotateCcw, Play, Pause } from 'lucide-react';
import { useEffect, useCallback, useMemo, useState, forwardRef, useImperativeHandle } from 'react';
import { ZoomIn, ZoomOut, Maximize2, Focus, RotateCcw, Play, Pause, Lightbulb, LightbulbOff } from 'lucide-react';
import { useSigma } from '../hooks/useSigma';
import { useAppState } from '../hooks/useAppState';
import { knowledgeGraphToGraphology, filterGraphByDepth, SigmaNodeAttributes, SigmaEdgeAttributes } from '../lib/graph-adapter';
@ -11,8 +11,28 @@ export interface GraphCanvasHandle {
}
export const GraphCanvas = forwardRef<GraphCanvasHandle>((_, ref) => {
const { graph, setSelectedNode, selectedNode: appSelectedNode, visibleLabels, openCodePanel, depthFilter, highlightedNodeIds } = useAppState();
const {
graph,
setSelectedNode,
selectedNode: appSelectedNode,
visibleLabels,
openCodePanel,
depthFilter,
highlightedNodeIds,
aiCitationHighlightedNodeIds,
aiToolHighlightedNodeIds,
isAIHighlightsEnabled,
toggleAIHighlights,
} = useAppState();
const [hoveredNodeName, setHoveredNodeName] = useState<string | null>(null);
const effectiveHighlightedNodeIds = useMemo(() => {
if (!isAIHighlightsEnabled) return highlightedNodeIds;
const next = new Set(highlightedNodeIds);
for (const id of aiCitationHighlightedNodeIds) next.add(id);
for (const id of aiToolHighlightedNodeIds) next.add(id);
return next;
}, [highlightedNodeIds, aiCitationHighlightedNodeIds, aiToolHighlightedNodeIds, isAIHighlightsEnabled]);
const handleNodeClick = useCallback((nodeId: string) => {
if (!graph) return;
@ -55,7 +75,7 @@ export const GraphCanvas = forwardRef<GraphCanvasHandle>((_, ref) => {
onNodeClick: handleNodeClick,
onNodeHover: handleNodeHover,
onStageClick: handleStageClick,
highlightedNodeIds,
highlightedNodeIds: effectiveHighlightedNodeIds,
});
// Expose focusNode to parent via ref
@ -244,6 +264,21 @@ export const GraphCanvas = forwardRef<GraphCanvasHandle>((_, ref) => {
{/* Query FAB */}
<QueryFAB />
{/* AI Highlights toggle - Top Right */}
<div className="absolute top-4 right-4 z-20">
<button
onClick={toggleAIHighlights}
className={
isAIHighlightsEnabled
? 'w-10 h-10 flex items-center justify-center bg-cyan-500/15 border border-cyan-400/40 rounded-lg text-cyan-200 hover:bg-cyan-500/20 hover:border-cyan-300/60 transition-colors'
: 'w-10 h-10 flex items-center justify-center bg-elevated border border-border-subtle rounded-lg text-text-muted hover:bg-hover hover:text-text-primary transition-colors'
}
title={isAIHighlightsEnabled ? 'Turn off AI highlights' : 'Turn on AI highlights'}
>
{isAIHighlightsEnabled ? <Lightbulb className="w-4 h-4" /> : <LightbulbOff className="w-4 h-4" />}
</button>
</div>
</div>
);
});

View file

@ -67,6 +67,12 @@ interface AppState {
// Query state
highlightedNodeIds: Set<string>;
setHighlightedNodeIds: (ids: Set<string>) => void;
// AI highlights (toggable)
aiCitationHighlightedNodeIds: Set<string>;
aiToolHighlightedNodeIds: Set<string>;
isAIHighlightsEnabled: boolean;
toggleAIHighlights: () => void;
clearAIToolHighlights: () => void;
queryResult: QueryResult | null;
setQueryResult: (result: QueryResult | null) => void;
clearQueryHighlights: () => void;
@ -165,6 +171,19 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
// Query state
const [highlightedNodeIds, setHighlightedNodeIds] = useState<Set<string>>(new Set());
const [queryResult, setQueryResult] = useState<QueryResult | null>(null);
// AI highlights (separate from user/query highlights)
const [aiCitationHighlightedNodeIds, setAICitationHighlightedNodeIds] = useState<Set<string>>(new Set());
const [aiToolHighlightedNodeIds, setAIToolHighlightedNodeIds] = useState<Set<string>>(new Set());
const [isAIHighlightsEnabled, setAIHighlightsEnabled] = useState(true);
const toggleAIHighlights = useCallback(() => {
setAIHighlightsEnabled(prev => !prev);
}, []);
const clearAIToolHighlights = useCallback(() => {
setAIToolHighlightedNodeIds(new Set());
}, []);
const clearQueryHighlights = useCallback(() => {
setHighlightedNodeIds(new Set());
@ -265,9 +284,9 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
// Auto-open panel when references are added
setCodePanelOpen(true);
// Also highlight in graph if nodeId provided
if (ref.nodeId) {
setHighlightedNodeIds(prev => new Set([...prev, ref.nodeId!]));
// Track AI highlights separately so they can be toggled off in the UI
if (ref.nodeId && ref.source === 'ai') {
setAICitationHighlightedNodeIds(prev => new Set([...prev, ref.nodeId!]));
}
}, []);
@ -277,18 +296,12 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
const removed = prev.filter(r => r.source === 'ai');
const kept = prev.filter(r => r.source !== 'ai');
// Update graph highlights: remove nodeIds that were ONLY highlighted via AI refs
// Remove citation-based AI highlights for removed refs
const removedNodeIds = new Set(removed.map(r => r.nodeId).filter(Boolean) as string[]);
if (removedNodeIds.size > 0) {
setHighlightedNodeIds(prevHighlights => {
const next = new Set(prevHighlights);
for (const nodeId of removedNodeIds) {
const stillReferenced = kept.some(r => r.nodeId === nodeId);
const stillInQuery = queryResult?.nodeIds?.includes(nodeId) ?? false;
if (!stillReferenced && !stillInQuery) {
next.delete(nodeId);
}
}
setAICitationHighlightedNodeIds(prevIds => {
const next = new Set(prevIds);
for (const id of removedNodeIds) next.delete(id);
return next;
});
}
@ -490,6 +503,8 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
// Refresh Code panel for the new question: keep user-pinned refs, clear old AI citations
clearAICodeReferences();
// Also clear previous tool-driven AI highlights (highlight_in_graph)
clearAIToolHighlights();
if (!isAgentReady) {
// Try to initialize first
@ -734,10 +749,10 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
}
if (matchedIds.size > 0) {
setHighlightedNodeIds(matchedIds);
setAIToolHighlightedNodeIds(matchedIds);
}
} else if (rawIds.length > 0) {
setHighlightedNodeIds(new Set(rawIds));
setAIToolHighlightedNodeIds(new Set(rawIds));
}
}
}
@ -763,7 +778,7 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
setIsChatLoading(false);
setCurrentToolCalls([]);
}
}, [chatMessages, isAgentReady, initializeAgent, resolveFilePath, findFileNodeId, addCodeReference, clearAICodeReferences]);
}, [chatMessages, isAgentReady, initializeAgent, resolveFilePath, findFileNodeId, addCodeReference, clearAICodeReferences, clearAIToolHighlights, graph]);
const clearChat = useCallback(() => {
setChatMessages([]);
@ -776,11 +791,11 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
const ref = prev.find(r => r.id === id);
const newRefs = prev.filter(r => r.id !== id);
// Remove highlight if this was the only reference to that node
if (ref?.nodeId) {
const stillReferenced = newRefs.some(r => r.nodeId === ref.nodeId);
// Remove AI citation highlight if this was the only AI reference to that node
if (ref?.nodeId && ref.source === 'ai') {
const stillReferenced = newRefs.some(r => r.nodeId === ref.nodeId && r.source === 'ai');
if (!stillReferenced) {
setHighlightedNodeIds(prev => {
setAICitationHighlightedNodeIds(prev => {
const next = new Set(prev);
next.delete(ref.nodeId!);
return next;
@ -833,6 +848,11 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
setDepthFilter,
highlightedNodeIds,
setHighlightedNodeIds,
aiCitationHighlightedNodeIds,
aiToolHighlightedNodeIds,
isAIHighlightsEnabled,
toggleAIHighlights,
clearAIToolHighlights,
queryResult,
setQueryResult,
clearQueryHighlights,