Skip to content

Commit a36c60e

Browse files
committed
Improve WEB UI
1 parent 6b7063e commit a36c60e

File tree

4 files changed

+178
-83
lines changed

4 files changed

+178
-83
lines changed

nextjs-frontend/src/app/api/comparison/analyze/route.ts

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -234,26 +234,36 @@ async function extractFunctions(content: string, language: string): Promise<Func
234234

235235
const lines = content.split('\n');
236236

237-
// Simple regex patterns for different languages
237+
// Improved regex patterns for different languages
238238
const patterns: Record<string, RegExp> = {
239-
javascript: /^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\([^)]*\)|^(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/,
240-
typescript: /^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\([^)]*\)|^(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/,
241-
python: /^def\s+(\w+)\s*\([^)]*\):/,
242-
java: /^(?:public|private|protected)?\s*(?:static\s+)?(?:\w+\s+)*(\w+)\s*\([^)]*\)\s*\{/,
243-
cpp: /^(?:\w+\s+)*(\w+)\s*\([^)]*\)\s*\{/,
244-
c: /^(?:\w+\s+)*(\w+)\s*\([^)]*\)\s*\{/
239+
javascript: /^(?:export\s+)?(?:async\s+)?function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\([^)]*\)|^(?:export\s+)?const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/,
240+
typescript: /^(?:export\s+)?(?:async\s+)?function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\([^)]*\)|^(?:export\s+)?const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/,
241+
python: /^def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\([^)]*\):/,
242+
java: /^(?:public|private|protected)?\s*(?:static\s+)?(?:\w+\s+)+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\([^)]*\)\s*\{/,
243+
cpp: /^(?:\w+\s+)+([a-zA-Z_][a-zA-Z0-9_]*)\s*\([^)]*\)\s*\{/,
244+
c: /^(?:\w+\s+)+([a-zA-Z_][a-zA-Z0-9_]*)\s*\([^)]*\)\s*\{/
245245
};
246246

247247
const pattern = patterns[language];
248248
if (!pattern) return functions;
249249

250+
// Language keywords to exclude
251+
const keywords = new Set([
252+
'if', 'else', 'for', 'while', 'do', 'switch', 'case', 'default', 'break', 'continue',
253+
'return', 'try', 'catch', 'finally', 'throw', 'new', 'delete', 'typeof', 'instanceof',
254+
'var', 'let', 'const', 'function', 'class', 'extends', 'implements', 'interface',
255+
'public', 'private', 'protected', 'static', 'abstract', 'final', 'override',
256+
'import', 'export', 'from', 'as', 'default', 'async', 'await', 'yield',
257+
'true', 'false', 'null', 'undefined', 'void', 'this', 'super'
258+
]);
259+
250260
for (let i = 0; i < lines.length; i++) {
251261
const line = lines[i].trim();
252262
const match = line.match(pattern);
253-
263+
254264
if (match) {
255265
const functionName = match[1] || match[2];
256-
if (functionName) {
266+
if (functionName && !keywords.has(functionName.toLowerCase())) {
257267
// Find the end of the function (simplified)
258268
let endLine = i;
259269
let braceCount = 0;
@@ -300,18 +310,31 @@ async function extractFunctions(content: string, language: string): Promise<Func
300310
function calculateComplexity(content: string): number {
301311
// Simplified cyclomatic complexity calculation
302312
const complexityKeywords = [
303-
'if', 'else', 'while', 'for', 'switch', 'case', 'catch', 'try', '&&', '||', '?'
313+
'if', 'else', 'while', 'for', 'switch', 'case', 'catch', 'try'
304314
];
305-
315+
316+
const complexityOperators = ['&&', '||', '?'];
317+
306318
let complexity = 1; // Base complexity
307-
319+
308320
for (const keyword of complexityKeywords) {
309-
const matches = content.match(new RegExp(`\\b${keyword}\\b`, 'g'));
321+
// Escape special regex characters and use word boundaries for keywords
322+
const escapedKeyword = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
323+
const matches = content.match(new RegExp(`\\b${escapedKeyword}\\b`, 'g'));
310324
if (matches) {
311325
complexity += matches.length;
312326
}
313327
}
314-
328+
329+
for (const operator of complexityOperators) {
330+
// Escape special regex characters for operators
331+
const escapedOperator = operator.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
332+
const matches = content.match(new RegExp(escapedOperator, 'g'));
333+
if (matches) {
334+
complexity += matches.length;
335+
}
336+
}
337+
315338
return complexity;
316339
}
317340

nextjs-frontend/src/app/page.tsx

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,22 @@ import { Button } from '@/components/ui/Button';
66
import { Input } from '@/components/ui/Input';
77
import { DirectoryPicker } from '@/components/filesystem/DirectoryPicker';
88
import { InteractiveDiffViewer } from '@/components/diff/InteractiveDiffViewer';
9-
import { comparisonService, ComparisonResult } from '@/services/comparisonService';
9+
import { comparisonService, ComparisonResult, ComparisonService } from '@/services/comparisonService';
10+
import { useResponsive } from '@/hooks/useResponsive';
1011
import { useQuery } from '@tanstack/react-query';
1112

1213
export default function HomePage() {
1314
const [sourceDirectory, setSourceDirectory] = useState('');
1415
const [targetDirectory, setTargetDirectory] = useState('');
16+
17+
// Load saved directories on mount
18+
useEffect(() => {
19+
const savedSource = localStorage.getItem('smartdiff-source-directory');
20+
const savedTarget = localStorage.getItem('smartdiff-target-directory');
21+
22+
if (savedSource) setSourceDirectory(savedSource);
23+
if (savedTarget) setTargetDirectory(savedTarget);
24+
}, []);
1525
const [activeView, setActiveView] = useState<'summary' | 'graph' | 'interactive' | 'analysis' | 'diff'>('summary');
1626
const [isComparing, setIsComparing] = useState(false);
1727
const [comparisonResult, setComparisonResult] = useState<ComparisonResult | null>(null);
@@ -293,38 +303,77 @@ export default function HomePage() {
293303
<div className="flex items-center justify-between mb-2">
294304
<span className="text-sm font-medium text-gray-700">Analysis Time</span>
295305
<span className="text-sm text-gray-600">
296-
{comparisonService.constructor.formatAnalysisTime(comparisonResult.analysisTime)}
306+
{ComparisonService.formatAnalysisTime(comparisonResult.analysisTime)}
297307
</span>
298308
</div>
299309
<div className="flex items-center justify-between">
300310
<span className="text-sm font-medium text-gray-700">Overall Similarity</span>
301311
<span className="text-sm text-gray-600">
302-
{(comparisonService.constructor.calculateOverallSimilarity(comparisonResult) * 100).toFixed(1)}%
312+
{(ComparisonService.calculateOverallSimilarity(comparisonResult) * 100).toFixed(1)}%
303313
</span>
304314
</div>
305315
</div>
306316

307-
{/* Recent Matches */}
317+
{/* Function Matches */}
308318
<div>
309319
<h3 className="text-lg font-semibold mb-3">Function Matches</h3>
310320
<div className="space-y-2">
311-
{comparisonResult.matches.slice(0, 5).map((match: any, index: number) => (
321+
{comparisonResult.functionMatches.slice(0, 10).map((match, index) => (
312322
<div key={index} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
313323
<div className="flex items-center gap-3">
314-
<span className="font-mono text-sm">{match.source_id}</span>
324+
<span className="font-mono text-sm">
325+
{match.sourceFunction?.name || 'N/A'}
326+
</span>
315327
<span className="text-gray-400"></span>
316-
<span className="font-mono text-sm">{match.target_id}</span>
328+
<span className="font-mono text-sm">
329+
{match.targetFunction?.name || 'N/A'}
330+
</span>
317331
</div>
318332
<div className="flex items-center gap-2">
319333
<span className="text-sm text-gray-600">
320-
{(match.similarity.overall_similarity * 100).toFixed(1)}% similar
334+
{(match.similarity * 100).toFixed(1)}% similar
335+
</span>
336+
<span className={`px-2 py-1 rounded text-xs ${
337+
match.type === 'identical' ? 'bg-green-100 text-green-800' :
338+
match.type === 'similar' ? 'bg-blue-100 text-blue-800' :
339+
match.type === 'renamed' ? 'bg-yellow-100 text-yellow-800' :
340+
match.type === 'moved' ? 'bg-purple-100 text-purple-800' :
341+
match.type === 'added' ? 'bg-emerald-100 text-emerald-800' :
342+
'bg-red-100 text-red-800'
343+
}`}>
344+
{match.type}
345+
</span>
346+
</div>
347+
</div>
348+
))}
349+
</div>
350+
</div>
351+
352+
{/* File Changes */}
353+
<div>
354+
<h3 className="text-lg font-semibold mb-3">File Changes</h3>
355+
<div className="space-y-2">
356+
{comparisonResult.fileChanges.slice(0, 10).map((change, index) => (
357+
<div key={index} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
358+
<div className="flex items-center gap-3">
359+
<span className="font-mono text-sm">
360+
{change.sourcePath || change.targetPath}
321361
</span>
362+
</div>
363+
<div className="flex items-center gap-2">
364+
{change.similarity && (
365+
<span className="text-sm text-gray-600">
366+
{(change.similarity * 100).toFixed(1)}% similar
367+
</span>
368+
)}
322369
<span className={`px-2 py-1 rounded text-xs ${
323-
match.match_type === 'Similar' ? 'bg-blue-100 text-blue-800' :
324-
match.match_type === 'Renamed' ? 'bg-yellow-100 text-yellow-800' :
325-
'bg-gray-100 text-gray-800'
370+
change.type === 'unchanged' ? 'bg-gray-100 text-gray-800' :
371+
change.type === 'modified' ? 'bg-blue-100 text-blue-800' :
372+
change.type === 'added' ? 'bg-green-100 text-green-800' :
373+
change.type === 'deleted' ? 'bg-red-100 text-red-800' :
374+
'bg-purple-100 text-purple-800'
326375
}`}>
327-
{match.match_type}
376+
{change.type}
328377
</span>
329378
</div>
330379
</div>

nextjs-frontend/src/components/diff/InteractiveDiffViewer.tsx

Lines changed: 43 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -50,78 +50,64 @@ export function InteractiveDiffViewer({ data, onFunctionSelect }: InteractiveDif
5050
const [selectedFunction, setSelectedFunction] = useState<string | null>(null);
5151
const [isFullscreen, setIsFullscreen] = useState(false);
5252

53-
// Generate mock diff data from the graph match result
53+
// Generate diff data from the real comparison result
5454
const diffSections = useMemo(() => {
55-
if (!data) return [];
55+
if (!data || !data.functionMatches) return [];
5656

5757
const sections: DiffSection[] = [];
5858

5959
// Process function matches
60-
data.matches.forEach((match, index) => {
61-
const sourceLines: DiffLine[] = [
62-
{ lineNumber: 1, content: `function ${match.source_id}() {`, type: 'unchanged' },
63-
{ lineNumber: 2, content: ' // Original implementation', type: 'removed' },
64-
{ lineNumber: 3, content: ' return originalValue;', type: 'removed' },
65-
{ lineNumber: 4, content: ' // Updated implementation', type: 'added' },
66-
{ lineNumber: 5, content: ' return updatedValue;', type: 'added' },
67-
{ lineNumber: 6, content: '}', type: 'unchanged' },
68-
];
60+
data.functionMatches.forEach((match, index) => {
61+
const sourceName = match.sourceFunction?.name || 'N/A';
62+
const targetName = match.targetFunction?.name || 'N/A';
63+
const sourceContent = match.sourceFunction?.content || '';
64+
const targetContent = match.targetFunction?.content || '';
6965

70-
const targetLines: DiffLine[] = [
71-
{ lineNumber: 1, content: `function ${match.target_id}() {`, type: 'unchanged' },
72-
{ lineNumber: 2, content: ' // Updated implementation', type: 'added' },
73-
{ lineNumber: 3, content: ' return updatedValue;', type: 'added' },
74-
{ lineNumber: 4, content: '}', type: 'unchanged' },
75-
];
66+
// Split content into lines for diff display
67+
let sourceLines: DiffLine[] = [];
68+
let targetLines: DiffLine[] = [];
7669

77-
sections.push({
78-
functionName: match.source_id,
79-
sourceLines,
80-
targetLines,
81-
similarity: match.similarity.overall_similarity,
82-
confidence: match.confidence,
83-
matchType: match.match_type,
84-
});
85-
});
86-
87-
// Process additions
88-
data.additions.forEach((funcId) => {
89-
const targetLines: DiffLine[] = [
90-
{ lineNumber: 1, content: `function ${funcId}() {`, type: 'added' },
91-
{ lineNumber: 2, content: ' // New function implementation', type: 'added' },
92-
{ lineNumber: 3, content: ' return newValue;', type: 'added' },
93-
{ lineNumber: 4, content: '}', type: 'added' },
94-
];
70+
if (sourceContent) {
71+
sourceLines = sourceContent.split('\n').map((line, i) => ({
72+
lineNumber: i + 1,
73+
content: line,
74+
type: match.type === 'deleted' ? 'removed' :
75+
match.changes?.bodyChanged ? 'removed' : 'unchanged'
76+
}));
77+
}
9578

96-
sections.push({
97-
functionName: funcId,
98-
sourceLines: [],
99-
targetLines,
100-
similarity: 0,
101-
confidence: 1,
102-
matchType: 'Addition',
103-
});
104-
});
79+
if (targetContent) {
80+
targetLines = targetContent.split('\n').map((line, i) => ({
81+
lineNumber: i + 1,
82+
content: line,
83+
type: match.type === 'added' ? 'added' :
84+
match.changes?.bodyChanged ? 'added' : 'unchanged'
85+
}));
86+
}
10587

106-
// Process deletions
107-
data.deletions.forEach((funcId) => {
108-
const sourceLines: DiffLine[] = [
109-
{ lineNumber: 1, content: `function ${funcId}() {`, type: 'removed' },
110-
{ lineNumber: 2, content: ' // Deleted function implementation', type: 'removed' },
111-
{ lineNumber: 3, content: ' return deletedValue;', type: 'removed' },
112-
{ lineNumber: 4, content: '}', type: 'removed' },
113-
];
88+
// If no content available, create placeholder
89+
if (sourceLines.length === 0 && targetLines.length === 0) {
90+
const placeholderLines = [
91+
{ lineNumber: 1, content: `// Function: ${sourceName}`, type: 'unchanged' as const },
92+
{ lineNumber: 2, content: '// Content not available for preview', type: 'unchanged' as const }
93+
];
94+
sourceLines = [...placeholderLines];
95+
targetLines = [...placeholderLines];
96+
}
11497

11598
sections.push({
116-
functionName: funcId,
99+
functionName: sourceName,
117100
sourceLines,
118-
targetLines: [],
119-
similarity: 0,
120-
confidence: 1,
121-
matchType: 'Deletion',
101+
targetLines,
102+
similarity: match.similarity,
103+
confidence: match.similarity, // Use similarity as confidence
104+
matchType: match.type,
122105
});
123106
});
124107

108+
// Process additions and deletions are already included in functionMatches
109+
// with type 'added' and 'deleted', so we don't need separate processing
110+
125111
return sections;
126112
}, [data]);
127113

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { useState, useEffect } from 'react';
2+
3+
interface ResponsiveState {
4+
isMobile: boolean;
5+
isTablet: boolean;
6+
isDesktop: boolean;
7+
}
8+
9+
export function useResponsive(): ResponsiveState {
10+
const [state, setState] = useState<ResponsiveState>({
11+
isMobile: false,
12+
isTablet: false,
13+
isDesktop: true,
14+
});
15+
16+
useEffect(() => {
17+
const checkResponsive = () => {
18+
const width = window.innerWidth;
19+
setState({
20+
isMobile: width < 768,
21+
isTablet: width >= 768 && width < 1024,
22+
isDesktop: width >= 1024,
23+
});
24+
};
25+
26+
// Check on mount
27+
checkResponsive();
28+
29+
// Add event listener
30+
window.addEventListener('resize', checkResponsive);
31+
32+
// Cleanup
33+
return () => window.removeEventListener('resize', checkResponsive);
34+
}, []);
35+
36+
return state;
37+
}

0 commit comments

Comments
 (0)