Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
785 changes: 776 additions & 9 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
"dependencies": {
"@huggingface/transformers": "^3.0.0",
"@isomorphic-git/lightning-fs": "^4.6.2",
"@langchain/core": "^0.3.0",
"@langchain/google-genai": "^0.1.0",
"@langchain/langgraph": "^0.2.74",
"@langchain/openai": "^0.3.0",
"@sigma/edge-curve": "^3.1.0",
"@tailwindcss/vite": "^4.1.18",
"axios": "^1.13.2",
Expand All @@ -24,6 +28,7 @@
"isomorphic-git": "^1.36.1",
"jszip": "^3.10.1",
"kuzu-wasm": "^0.11.1",
"langchain": "^0.3.37",
"lru-cache": "^11.2.4",
"lucide-react": "^0.562.0",
"react": "^18.3.1",
Expand All @@ -36,7 +41,7 @@
"vite-plugin-top-level-await": "^1.6.0",
"vite-plugin-wasm": "^3.5.0",
"web-tree-sitter": "^0.20.8",
"zod": "^4.1.13"
"zod": "^3.25.76"
},
"devDependencies": {
"@babel/types": "^7.28.5",
Expand Down
47 changes: 45 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { LoadingOverlay } from './components/LoadingOverlay';
import { Header } from './components/Header';
import { GraphCanvas, GraphCanvasHandle } from './components/GraphCanvas';
import { RightPanel } from './components/RightPanel';
import { SettingsPanel } from './components/SettingsPanel';
import { StatusBar } from './components/StatusBar';
import { FileTreePanel } from './components/FileTreePanel';
import { FileEntry } from './services/zip';
Expand All @@ -21,6 +22,12 @@ const AppContent = () => {
isRightPanelOpen,
runPipeline,
runPipelineFromFiles,
isSettingsPanelOpen,
setSettingsPanelOpen,
refreshLLMSettings,
initializeAgent,
startEmbeddings,
embeddingStatus,
} = useAppState();

const graphCanvasRef = useRef<GraphCanvasHandle>(null);
Expand All @@ -38,6 +45,17 @@ const AppContent = () => {
setGraph(result.graph);
setFileContents(result.fileContents);
setViewMode('exploring');

// Auto-start embeddings pipeline in background
// Uses WebGPU if available, falls back to WASM
startEmbeddings().catch((err) => {
// WebGPU not available - try WASM fallback silently
if (err?.name === 'WebGPUNotAvailableError' || err?.message?.includes('WebGPU')) {
startEmbeddings('wasm').catch(console.warn);
} else {
console.warn('Embeddings auto-start failed:', err);
}
});
} catch (error) {
console.error('Pipeline error:', error);
setProgress({
Expand All @@ -51,7 +69,7 @@ const AppContent = () => {
setProgress(null);
}, 3000);
}
}, [setViewMode, setGraph, setFileContents, setProgress, setProjectName, runPipeline]);
}, [setViewMode, setGraph, setFileContents, setProgress, setProjectName, runPipeline, startEmbeddings]);

const handleGitClone = useCallback(async (files: FileEntry[]) => {
// Extract project name from first file path (e.g., "owner-repo-123/src/..." -> "owner-repo")
Expand All @@ -69,6 +87,17 @@ const AppContent = () => {
setGraph(result.graph);
setFileContents(result.fileContents);
setViewMode('exploring');

// Auto-start embeddings pipeline in background
// Uses WebGPU if available, falls back to WASM
startEmbeddings().catch((err) => {
// WebGPU not available - try WASM fallback silently
if (err?.name === 'WebGPUNotAvailableError' || err?.message?.includes('WebGPU')) {
startEmbeddings('wasm').catch(console.warn);
} else {
console.warn('Embeddings auto-start failed:', err);
}
});
} catch (error) {
console.error('Pipeline error:', error);
setProgress({
Expand All @@ -82,12 +111,19 @@ const AppContent = () => {
setProgress(null);
}, 3000);
}
}, [setViewMode, setGraph, setFileContents, setProgress, setProjectName, runPipelineFromFiles]);
}, [setViewMode, setGraph, setFileContents, setProgress, setProjectName, runPipelineFromFiles, startEmbeddings]);

const handleFocusNode = useCallback((nodeId: string) => {
graphCanvasRef.current?.focusNode(nodeId);
}, []);

// Handle settings saved - refresh and reinitialize agent
// NOTE: Must be defined BEFORE any conditional returns (React hooks rule)
const handleSettingsSaved = useCallback(() => {
refreshLLMSettings();
initializeAgent();
}, [refreshLLMSettings, initializeAgent]);

// Render based on view mode
if (viewMode === 'onboarding') {
return <DropZone onFileSelect={handleFileSelect} onGitClone={handleGitClone} />;
Expand Down Expand Up @@ -116,6 +152,13 @@ const AppContent = () => {
</main>

<StatusBar />

{/* Settings Panel (modal) */}
<SettingsPanel
isOpen={isSettingsPanelOpen}
onClose={() => setSettingsPanelOpen(false)}
onSettingsSaved={handleSettingsSaved}
/>
</div>
);
};
Expand Down
3 changes: 0 additions & 3 deletions src/components/DropZone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,6 @@ export const DropZone = ({ onFileSelect, onGitClone }: DropZoneProps) => {
<span className="px-3 py-1.5 bg-elevated border border-border-subtle rounded-md">
.zip
</span>
<span className="px-3 py-1.5 bg-elevated border border-border-subtle rounded-md">
up to 50MB
</span>
</div>
</div>
)}
Expand Down
15 changes: 14 additions & 1 deletion src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Search, Settings, HelpCircle, Sparkles } from 'lucide-react';
import { Search, Settings, HelpCircle, Sparkles, Github, Star } from 'lucide-react';
import { useAppState } from '../hooks/useAppState';
import { useState, useMemo, useRef, useEffect } from 'react';
import { GraphNode } from '../core/graph/types';
Expand Down Expand Up @@ -183,6 +183,19 @@ export const Header = ({ onFocusNode }: HeaderProps) => {

{/* Right section */}
<div className="flex items-center gap-2">
{/* GitHub Star Button */}
<a
href="https://github.com/abhigyanpatwari/GitNexus"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 px-3.5 py-2 bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-500 hover:to-pink-500 rounded-lg text-white text-sm font-medium shadow-lg hover:shadow-xl hover:-translate-y-0.5 transition-all duration-200 group"
>
<Github className="w-4 h-4" />
<span className="hidden sm:inline">Star if cool</span>
<Star className="w-3.5 h-3.5 group-hover:fill-yellow-300 group-hover:text-yellow-300 transition-all" />
<span className="hidden sm:inline">✨</span>
</a>

{/* Stats */}
{graph && (
<div className="flex items-center gap-4 mr-2 text-xs text-text-muted">
Expand Down
145 changes: 109 additions & 36 deletions src/components/RightPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { useState, useMemo } from 'react';
import { X, Send, Sparkles, User, FileCode, Hash, GitBranch, Code, MessageSquare, PanelRightClose } from 'lucide-react';
import {
X, Send, Sparkles, User, FileCode, Hash, GitBranch, Code, MessageSquare,
PanelRightClose, Loader2, Settings, AlertTriangle
} from 'lucide-react';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
import ReactMarkdown from 'react-markdown';
import { useAppState } from '../hooks/useAppState';
import { NODE_COLORS } from '../lib/constants';
import { ToolCallCard } from './ToolCallCard';

// Custom syntax theme
const customTheme = {
Expand All @@ -23,13 +28,6 @@ const customTheme = {
},
};

// Chat message interface
interface Message {
id: string;
role: 'user' | 'assistant';
content: string;
}

export const RightPanel = () => {
const {
selectedNode,
Expand All @@ -40,10 +38,18 @@ export const RightPanel = () => {
setRightPanelOpen,
rightPanelTab,
setRightPanelTab,
// LLM / chat state
chatMessages,
isChatLoading,
currentToolCalls,
agentError,
isAgentReady,
isAgentInitializing,
setSettingsPanelOpen,
sendChatMessage,
clearChat,
} = useAppState();

// Chat state
const [messages, setMessages] = useState<Message[]>([]);
const [chatInput, setChatInput] = useState('');

// Get source code for selected node
Expand Down Expand Up @@ -90,27 +96,11 @@ export const RightPanel = () => {
}, [selectedNode, graph]);

// Chat handlers
const handleSendMessage = () => {
const handleSendMessage = async () => {
if (!chatInput.trim()) return;

const userMessage: Message = {
id: Date.now().toString(),
role: 'user',
content: chatInput.trim(),
};

setMessages(prev => [...prev, userMessage]);
const text = chatInput.trim();
setChatInput('');

// Simulate AI response
setTimeout(() => {
const aiMessage: Message = {
id: (Date.now() + 1).toString(),
role: 'assistant',
content: 'This is a placeholder response. AI integration coming soon! I will be able to help you understand the codebase, find specific functions, and explain how different parts connect.',
};
setMessages(prev => [...prev, aiMessage]);
}, 500);
await sendChatMessage(text);
};

const handleKeyDown = (e: React.KeyboardEvent) => {
Expand Down Expand Up @@ -289,11 +279,51 @@ export const RightPanel = () => {
<Sparkles className="w-4 h-4 text-accent" />
<span className="font-medium text-sm">Nexus AI</span>
<span className="text-xs text-text-muted">• Ask about the codebase</span>
<div className="ml-auto flex items-center gap-2">
{!isAgentReady && (
<span className="text-[11px] px-2 py-1 rounded-full bg-amber-500/15 text-amber-300 border border-amber-500/30">
Configure AI
</span>
)}
{isAgentInitializing && (
<span className="text-[11px] px-2 py-1 rounded-full bg-surface border border-border-subtle flex items-center gap-1 text-text-muted">
<Loader2 className="w-3 h-3 animate-spin" /> Connecting
</span>
)}
<button
onClick={() => setSettingsPanelOpen(true)}
className="p-1.5 rounded-md text-text-muted hover:text-text-primary hover:bg-hover transition-colors"
title="AI Settings"
>
<Settings className="w-4 h-4" />
</button>
</div>
</div>

{/* Status / errors */}
{agentError && (
<div className="px-4 py-3 bg-rose-500/10 border-b border-rose-500/30 text-rose-100 text-sm flex items-center gap-2">
<AlertTriangle className="w-4 h-4" />
<span>{agentError}</span>
</div>
)}

{/* Active tool calls - shown at top during execution */}
{currentToolCalls.length > 0 && (
<div className="px-4 py-3 bg-elevated/60 border-b border-border-subtle space-y-2">
<div className="text-[10px] uppercase tracking-wider text-text-muted flex items-center gap-1">
<Loader2 className="w-3 h-3 animate-spin" />
<span>Working...</span>
</div>
{currentToolCalls.map(tc => (
<ToolCallCard key={tc.id} toolCall={tc} defaultExpanded={false} />
))}
</div>
)}

{/* Messages */}
<div className="flex-1 overflow-y-auto p-4 scrollbar-thin">
{messages.length === 0 ? (
{chatMessages.length === 0 ? (
<div className="flex flex-col items-center justify-center h-full text-center px-4">
<div className="w-14 h-14 mb-4 flex items-center justify-center bg-gradient-to-br from-accent to-node-interface rounded-xl shadow-glow text-2xl">
🧠
Expand All @@ -318,7 +348,7 @@ export const RightPanel = () => {
</div>
) : (
<div className="flex flex-col gap-4">
{messages.map((message) => (
{chatMessages.map((message) => (
<div
key={message.id}
className={`flex gap-3 ${message.role === 'user' ? 'flex-row-reverse' : ''} animate-fade-in`}
Expand All @@ -339,11 +369,41 @@ export const RightPanel = () => {
<div className={`
max-w-[85%] px-3.5 py-2.5 rounded-xl text-sm leading-relaxed
${message.role === 'assistant'
? 'bg-elevated border border-border-subtle text-text-primary'
? 'bg-elevated border border-border-subtle text-text-primary prose prose-sm prose-invert max-w-none'
: 'bg-accent text-white'
}
`}>
{message.content}
{message.role === 'assistant' ? (
<ReactMarkdown
components={{
code: ({ className, children, ...props }) => {
const isInline = !className;
return isInline ? (
<code className="px-1 py-0.5 bg-surface rounded text-accent font-mono text-xs" {...props}>
{children}
</code>
) : (
<code className="block p-2 bg-surface rounded text-xs font-mono overflow-x-auto" {...props}>
{children}
</code>
);
},
pre: ({ children }) => <>{children}</>,
}}
>
{message.content}
</ReactMarkdown>
) : (
message.content
)}
{/* Tool calls shown as expandable cards */}
{message.toolCalls && message.toolCalls.length > 0 && (
<div className="mt-3 space-y-2">
{message.toolCalls.map(tc => (
<ToolCallCard key={tc.id} toolCall={tc} defaultExpanded={false} />
))}
</div>
)}
</div>
</div>
))}
Expand All @@ -362,14 +422,27 @@ export const RightPanel = () => {
rows={1}
className="flex-1 bg-transparent border-none outline-none text-sm text-text-primary placeholder:text-text-muted resize-none max-h-24"
/>
<button
onClick={clearChat}
className="px-2 py-1 text-xs text-text-muted hover:text-text-primary transition-colors"
title="Clear chat"
>
Clear
</button>
<button
onClick={handleSendMessage}
disabled={!chatInput.trim()}
className="w-7 h-7 flex items-center justify-center bg-accent rounded-md text-white transition-all hover:bg-accent-dim disabled:opacity-50 disabled:cursor-not-allowed"
disabled={!chatInput.trim() || isChatLoading || isAgentInitializing}
className="w-9 h-9 flex items-center justify-center bg-accent rounded-md text-white transition-all hover:bg-accent-dim disabled:opacity-50 disabled:cursor-not-allowed"
>
<Send className="w-3.5 h-3.5" />
{isChatLoading ? <Loader2 className="w-4 h-4 animate-spin" /> : <Send className="w-3.5 h-3.5" />}
</button>
</div>
{!isAgentReady && !isAgentInitializing && (
<div className="mt-2 text-xs text-amber-200 flex items-center gap-2">
<AlertTriangle className="w-3.5 h-3.5" />
<span>Configure an LLM provider to enable chat.</span>
</div>
)}
</div>
</div>
)}
Expand Down
Loading