Asish Karthikeya Gogineni commited on
Commit
7a7972b
·
1 Parent(s): 23a9623

feat: Add Next.js frontend with Framer Motion animations

Browse files

## New Frontend (frontend/)
- Next.js 14 with App Router and TypeScript
- Framer Motion page transitions and animations
- Glassmorphic dark theme matching Cursor/Antigravity
- Animated sidebar navigation
- All 4 modes: Chat, Search, Refactor, Generate

## Pages
- / - Home with drag-and-drop upload
- /chat - Animated chat messages with source chips
- /search - Regex search with expandable results
- /refactor - Pattern templates with diff preview
- /generate - Feature generation with code display

## Tech Stack
- Next.js 14, TypeScript, Tailwind CSS v4
- Framer Motion, React Query, Lucide Icons
- API proxy to FastAPI backend

frontend/.gitignore ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.*
7
+ .yarn/*
8
+ !.yarn/patches
9
+ !.yarn/plugins
10
+ !.yarn/releases
11
+ !.yarn/versions
12
+
13
+ # testing
14
+ /coverage
15
+
16
+ # next.js
17
+ /.next/
18
+ /out/
19
+
20
+ # production
21
+ /build
22
+
23
+ # misc
24
+ .DS_Store
25
+ *.pem
26
+
27
+ # debug
28
+ npm-debug.log*
29
+ yarn-debug.log*
30
+ yarn-error.log*
31
+ .pnpm-debug.log*
32
+
33
+ # env files (can opt-in for committing if needed)
34
+ .env*
35
+
36
+ # vercel
37
+ .vercel
38
+
39
+ # typescript
40
+ *.tsbuildinfo
41
+ next-env.d.ts
frontend/README.md ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2
+
3
+ ## Getting Started
4
+
5
+ First, run the development server:
6
+
7
+ ```bash
8
+ npm run dev
9
+ # or
10
+ yarn dev
11
+ # or
12
+ pnpm dev
13
+ # or
14
+ bun dev
15
+ ```
16
+
17
+ Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18
+
19
+ You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20
+
21
+ This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22
+
23
+ ## Learn More
24
+
25
+ To learn more about Next.js, take a look at the following resources:
26
+
27
+ - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28
+ - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29
+
30
+ You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31
+
32
+ ## Deploy on Vercel
33
+
34
+ The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35
+
36
+ Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
frontend/eslint.config.mjs ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig, globalIgnores } from "eslint/config";
2
+ import nextVitals from "eslint-config-next/core-web-vitals";
3
+ import nextTs from "eslint-config-next/typescript";
4
+
5
+ const eslintConfig = defineConfig([
6
+ ...nextVitals,
7
+ ...nextTs,
8
+ // Override default ignores of eslint-config-next.
9
+ globalIgnores([
10
+ // Default ignores of eslint-config-next:
11
+ ".next/**",
12
+ "out/**",
13
+ "build/**",
14
+ "next-env.d.ts",
15
+ ]),
16
+ ]);
17
+
18
+ export default eslintConfig;
frontend/next.config.ts ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ // API rewrites to proxy to FastAPI backend
5
+ async rewrites() {
6
+ return [
7
+ {
8
+ source: '/api/:path*',
9
+ destination: 'http://localhost:8000/api/:path*',
10
+ },
11
+ ];
12
+ },
13
+
14
+ // Image optimization
15
+ images: {
16
+ remotePatterns: [],
17
+ },
18
+ };
19
+
20
+ export default nextConfig;
frontend/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
frontend/package.json ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "frontend",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "eslint"
10
+ },
11
+ "dependencies": {
12
+ "@tanstack/react-query": "^5.90.20",
13
+ "axios": "^1.13.4",
14
+ "framer-motion": "^12.29.2",
15
+ "lucide-react": "^0.563.0",
16
+ "next": "16.1.6",
17
+ "prism-react-renderer": "^2.4.1",
18
+ "react": "19.2.3",
19
+ "react-dom": "19.2.3",
20
+ "zustand": "^5.0.10"
21
+ },
22
+ "devDependencies": {
23
+ "@tailwindcss/postcss": "^4",
24
+ "@types/node": "^20",
25
+ "@types/react": "^19",
26
+ "@types/react-dom": "^19",
27
+ "eslint": "^9",
28
+ "eslint-config-next": "16.1.6",
29
+ "tailwindcss": "^4",
30
+ "typescript": "^5"
31
+ }
32
+ }
frontend/postcss.config.mjs ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
frontend/public/file.svg ADDED
frontend/public/globe.svg ADDED
frontend/public/next.svg ADDED
frontend/public/vercel.svg ADDED
frontend/public/window.svg ADDED
frontend/src/app/chat/page.tsx ADDED
@@ -0,0 +1,221 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { motion, AnimatePresence } from 'framer-motion';
4
+ import { useState, useRef, useEffect } from 'react';
5
+ import { Send, Bot, User, FileCode, Sparkles } from 'lucide-react';
6
+
7
+ interface Message {
8
+ id: string;
9
+ role: 'user' | 'assistant';
10
+ content: string;
11
+ sources?: string[];
12
+ }
13
+
14
+ export default function ChatPage() {
15
+ const [messages, setMessages] = useState<Message[]>([]);
16
+ const [input, setInput] = useState('');
17
+ const [isLoading, setIsLoading] = useState(false);
18
+ const messagesEndRef = useRef<HTMLDivElement>(null);
19
+
20
+ const scrollToBottom = () => {
21
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
22
+ };
23
+
24
+ useEffect(() => {
25
+ scrollToBottom();
26
+ }, [messages]);
27
+
28
+ const handleSubmit = async (e: React.FormEvent) => {
29
+ e.preventDefault();
30
+ if (!input.trim() || isLoading) return;
31
+
32
+ const userMessage: Message = {
33
+ id: Date.now().toString(),
34
+ role: 'user',
35
+ content: input,
36
+ };
37
+
38
+ setMessages((prev) => [...prev, userMessage]);
39
+ setInput('');
40
+ setIsLoading(true);
41
+
42
+ try {
43
+ const response = await fetch('http://localhost:8000/api/chat', {
44
+ method: 'POST',
45
+ headers: { 'Content-Type': 'application/json' },
46
+ body: JSON.stringify({ message: input }),
47
+ });
48
+
49
+ const data = await response.json();
50
+
51
+ const assistantMessage: Message = {
52
+ id: (Date.now() + 1).toString(),
53
+ role: 'assistant',
54
+ content: data.answer || data.response || 'No response received',
55
+ sources: data.sources || [],
56
+ };
57
+
58
+ setMessages((prev) => [...prev, assistantMessage]);
59
+ } catch (error) {
60
+ setMessages((prev) => [
61
+ ...prev,
62
+ {
63
+ id: (Date.now() + 1).toString(),
64
+ role: 'assistant',
65
+ content: 'Error connecting to backend. Please ensure the API is running.',
66
+ },
67
+ ]);
68
+ } finally {
69
+ setIsLoading(false);
70
+ }
71
+ };
72
+
73
+ const suggestions = [
74
+ 'Explain how authentication works',
75
+ 'Find all database queries',
76
+ 'What are the main entry points?',
77
+ 'Show me the API endpoints',
78
+ ];
79
+
80
+ return (
81
+ <div className="flex flex-col h-screen">
82
+ {/* Header */}
83
+ <motion.header
84
+ initial={{ y: -20, opacity: 0 }}
85
+ animate={{ y: 0, opacity: 1 }}
86
+ className="bg-slate-900/60 backdrop-blur-xl border border-white/10 rounded-2xl p-6 m-4 mb-0 flex items-center justify-between"
87
+ >
88
+ <div className="flex items-center gap-3">
89
+ <div className="w-10 h-10 rounded-xl bg-gradient-to-br from-sky-400 to-violet-500 flex items-center justify-center">
90
+ 💬
91
+ </div>
92
+ <div>
93
+ <h1 className="font-bold text-lg">Chat Mode</h1>
94
+ <p className="text-sm text-slate-400">Ask questions about your codebase</p>
95
+ </div>
96
+ </div>
97
+ </motion.header>
98
+
99
+ {/* Messages */}
100
+ <div className="flex-1 overflow-y-auto p-4 space-y-4">
101
+ {messages.length === 0 ? (
102
+ <motion.div
103
+ initial={{ opacity: 0, y: 20 }}
104
+ animate={{ opacity: 1, y: 0 }}
105
+ className="flex flex-col items-center justify-center h-full text-center"
106
+ >
107
+ <Sparkles className="w-16 h-16 text-slate-600 mb-4" />
108
+ <h2 className="text-2xl font-bold bg-gradient-to-r from-sky-400 to-violet-400 bg-clip-text text-transparent mb-2">Ask anything about your code</h2>
109
+ <p className="text-slate-400 mb-8 max-w-md">
110
+ I can explain functions, find patterns, suggest improvements, and more.
111
+ </p>
112
+
113
+ <div className="flex flex-wrap gap-2 justify-center max-w-xl">
114
+ {suggestions.map((suggestion, i) => (
115
+ <motion.button
116
+ key={suggestion}
117
+ initial={{ opacity: 0, scale: 0.9 }}
118
+ animate={{ opacity: 1, scale: 1 }}
119
+ transition={{ delay: i * 0.1 }}
120
+ whileHover={{ scale: 1.05 }}
121
+ whileTap={{ scale: 0.95 }}
122
+ onClick={() => setInput(suggestion)}
123
+ className="px-4 py-2 bg-slate-900/60 backdrop-blur-xl border border-white/10 rounded-xl rounded-full text-sm text-slate-300 hover:border-sky-500/50 transition-colors"
124
+ >
125
+ {suggestion}
126
+ </motion.button>
127
+ ))}
128
+ </div>
129
+ </motion.div>
130
+ ) : (
131
+ <AnimatePresence>
132
+ {messages.map((message, i) => (
133
+ <motion.div
134
+ key={message.id}
135
+ initial={{ opacity: 0, y: 20 }}
136
+ animate={{ opacity: 1, y: 0 }}
137
+ transition={{ delay: i * 0.05 }}
138
+ className={`flex gap-4 ${message.role === 'user' ? 'flex-row-reverse' : ''}`}
139
+ >
140
+ <div className={`w-10 h-10 rounded-xl flex items-center justify-center shrink-0 ${message.role === 'user'
141
+ ? 'bg-violet-500/20 text-violet-400'
142
+ : 'bg-sky-500/20 text-sky-400'
143
+ }`}>
144
+ {message.role === 'user' ? <User className="w-5 h-5" /> : <Bot className="w-5 h-5" />}
145
+ </div>
146
+
147
+ <div className={`bg-slate-900/60 backdrop-blur-xl border border-white/10 rounded-2xl p-6 max-w-[70%] ${message.role === 'user'
148
+ ? 'bg-violet-500/10 border-violet-500/20'
149
+ : 'border-sky-500/20'
150
+ }`}>
151
+ {/* Sources */}
152
+ {message.sources && message.sources.length > 0 && (
153
+ <div className="flex flex-wrap gap-2 mb-3 pb-3 border-b border-white/10">
154
+ {message.sources.map((source, i) => (
155
+ <span key={i} className="inline-flex items-center gap-1 px-2 py-1 bg-slate-800 rounded text-xs text-slate-400">
156
+ <FileCode className="w-3 h-3" />
157
+ {source}
158
+ </span>
159
+ ))}
160
+ </div>
161
+ )}
162
+
163
+ <p className="whitespace-pre-wrap">{message.content}</p>
164
+ </div>
165
+ </motion.div>
166
+ ))}
167
+ </AnimatePresence>
168
+ )}
169
+
170
+ {isLoading && (
171
+ <motion.div
172
+ initial={{ opacity: 0 }}
173
+ animate={{ opacity: 1 }}
174
+ className="flex gap-4"
175
+ >
176
+ <div className="w-10 h-10 rounded-xl bg-sky-500/20 text-sky-400 flex items-center justify-center">
177
+ <Bot className="w-5 h-5" />
178
+ </div>
179
+ <div className="bg-slate-900/60 backdrop-blur-xl border border-white/10 rounded-2xl p-6 border-sky-500/20">
180
+ <div className="flex gap-1">
181
+ <span className="w-2 h-2 bg-sky-400 rounded-full animate-bounce" style={{ animationDelay: '0ms' }} />
182
+ <span className="w-2 h-2 bg-sky-400 rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
183
+ <span className="w-2 h-2 bg-sky-400 rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
184
+ </div>
185
+ </div>
186
+ </motion.div>
187
+ )}
188
+
189
+ <div ref={messagesEndRef} />
190
+ </div>
191
+
192
+ {/* Input */}
193
+ <motion.form
194
+ initial={{ y: 20, opacity: 0 }}
195
+ animate={{ y: 0, opacity: 1 }}
196
+ onSubmit={handleSubmit}
197
+ className="p-4"
198
+ >
199
+ <div className="bg-slate-900/60 backdrop-blur-xl border border-white/10 rounded-2xl p-6 flex items-center gap-4 !p-3">
200
+ <input
201
+ type="text"
202
+ value={input}
203
+ onChange={(e) => setInput(e.target.value)}
204
+ placeholder="Ask about your codebase..."
205
+ className="flex-1 bg-transparent border-none outline-none text-slate-200 placeholder:text-slate-500"
206
+ disabled={isLoading}
207
+ />
208
+ <motion.button
209
+ whileHover={{ scale: 1.1 }}
210
+ whileTap={{ scale: 0.9 }}
211
+ type="submit"
212
+ disabled={isLoading || !input.trim()}
213
+ className="w-10 h-10 bg-gradient-to-r from-sky-500 to-violet-500 rounded-xl flex items-center justify-center disabled:opacity-50 disabled:cursor-not-allowed"
214
+ >
215
+ <Send className="w-5 h-5" />
216
+ </motion.button>
217
+ </div>
218
+ </motion.form>
219
+ </div>
220
+ );
221
+ }
frontend/src/app/favicon.ico ADDED
frontend/src/app/generate/page.tsx ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { motion } from 'framer-motion';
4
+ import { useState } from 'react';
5
+ import { Sparkles, FileCode, Save, MessageSquare } from 'lucide-react';
6
+
7
+ export default function GeneratePage() {
8
+ const [featureDesc, setFeatureDesc] = useState('');
9
+ const [framework, setFramework] = useState('Auto-detect');
10
+ const [includeTests, setIncludeTests] = useState(true);
11
+ const [includeDocs, setIncludeDocs] = useState(true);
12
+ const [isGenerating, setIsGenerating] = useState(false);
13
+ const [result, setResult] = useState<string | null>(null);
14
+
15
+ const examples = [
16
+ 'Create a user authentication system with JWT tokens',
17
+ 'Build a REST API endpoint for file uploads',
18
+ 'Add a caching layer with Redis',
19
+ 'Generate unit tests for the auth module',
20
+ ];
21
+
22
+ const handleGenerate = async (e: React.FormEvent) => {
23
+ e.preventDefault();
24
+ if (!featureDesc.trim()) return;
25
+
26
+ setIsGenerating(true);
27
+
28
+ try {
29
+ const response = await fetch('http://localhost:8000/api/chat', {
30
+ method: 'POST',
31
+ headers: { 'Content-Type': 'application/json' },
32
+ body: JSON.stringify({
33
+ message: `Generate a complete implementation for: ${featureDesc}. Framework: ${framework}. Include tests: ${includeTests}. Include docs: ${includeDocs}.`,
34
+ }),
35
+ });
36
+
37
+ const data = await response.json();
38
+ setResult(data.answer || data.response || 'No response received');
39
+ } catch (error) {
40
+ console.error('Generation failed:', error);
41
+ setResult('Error connecting to backend');
42
+ } finally {
43
+ setIsGenerating(false);
44
+ }
45
+ };
46
+
47
+ return (
48
+ <div className="flex flex-col h-screen">
49
+ {/* Header */}
50
+ <motion.header
51
+ initial={{ y: -20, opacity: 0 }}
52
+ animate={{ y: 0, opacity: 1 }}
53
+ className="bg-slate-900/60 backdrop-blur-xl border border-white/10 rounded-2xl p-6 m-4 mb-0"
54
+ >
55
+ <div className="flex items-center gap-3 mb-4">
56
+ <div className="w-10 h-10 rounded-xl bg-gradient-to-br from-violet-400 to-fuchsia-500 flex items-center justify-center">
57
+
58
+ </div>
59
+ <div>
60
+ <h1 className="font-bold text-lg">Generate Mode</h1>
61
+ <p className="text-sm text-slate-400">Create complete features from descriptions</p>
62
+ </div>
63
+ </div>
64
+
65
+ {/* Examples */}
66
+ <div className="flex flex-wrap gap-2 mb-4">
67
+ {examples.map((example) => (
68
+ <motion.button
69
+ key={example}
70
+ whileHover={{ scale: 1.02 }}
71
+ whileTap={{ scale: 0.98 }}
72
+ onClick={() => setFeatureDesc(example)}
73
+ className="px-3 py-1.5 bg-slate-800 rounded-lg text-sm text-slate-300 hover:bg-slate-700 transition-colors"
74
+ >
75
+ {example.slice(0, 40)}...
76
+ </motion.button>
77
+ ))}
78
+ </div>
79
+
80
+ {/* Form */}
81
+ <form onSubmit={handleGenerate} className="space-y-4">
82
+ <div>
83
+ <label className="block text-sm text-slate-400 mb-2">Feature Description</label>
84
+ <textarea
85
+ value={featureDesc}
86
+ onChange={(e) => setFeatureDesc(e.target.value)}
87
+ placeholder="Describe the feature you want to build in detail..."
88
+ rows={4}
89
+ className="w-full px-4 py-3 bg-slate-800/50 border border-slate-700 rounded-xl focus:border-violet-500 focus:outline-none transition-colors resize-none"
90
+ />
91
+ </div>
92
+
93
+ <div className="flex gap-4">
94
+ <div className="flex-1">
95
+ <label className="block text-sm text-slate-400 mb-2">Framework</label>
96
+ <select
97
+ value={framework}
98
+ onChange={(e) => setFramework(e.target.value)}
99
+ className="w-full px-4 py-3 bg-slate-800/50 border border-slate-700 rounded-xl focus:border-violet-500 focus:outline-none transition-colors"
100
+ >
101
+ <option>Auto-detect</option>
102
+ <option>FastAPI</option>
103
+ <option>Flask</option>
104
+ <option>Django</option>
105
+ <option>Express.js</option>
106
+ <option>React</option>
107
+ </select>
108
+ </div>
109
+
110
+ <label className="flex items-center gap-2 px-4 py-3 bg-slate-800/50 rounded-xl cursor-pointer">
111
+ <input
112
+ type="checkbox"
113
+ checked={includeTests}
114
+ onChange={(e) => setIncludeTests(e.target.checked)}
115
+ className="w-4 h-4 accent-violet-500"
116
+ />
117
+ <span className="text-sm text-slate-300">Tests</span>
118
+ </label>
119
+
120
+ <label className="flex items-center gap-2 px-4 py-3 bg-slate-800/50 rounded-xl cursor-pointer">
121
+ <input
122
+ type="checkbox"
123
+ checked={includeDocs}
124
+ onChange={(e) => setIncludeDocs(e.target.checked)}
125
+ className="w-4 h-4 accent-violet-500"
126
+ />
127
+ <span className="text-sm text-slate-300">Docs</span>
128
+ </label>
129
+ </div>
130
+
131
+ <motion.button
132
+ whileHover={{ scale: 1.02 }}
133
+ whileTap={{ scale: 0.98 }}
134
+ type="submit"
135
+ disabled={isGenerating || !featureDesc.trim()}
136
+ className="w-full py-3 bg-gradient-to-r from-violet-500 to-fuchsia-500 rounded-xl font-semibold flex items-center justify-center gap-2 disabled:opacity-50"
137
+ >
138
+ {isGenerating ? (
139
+ <>
140
+ <div className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
141
+ Generating...
142
+ </>
143
+ ) : (
144
+ <>
145
+ <Sparkles className="w-5 h-5" />
146
+ Generate Feature
147
+ </>
148
+ )}
149
+ </motion.button>
150
+ </form>
151
+ </motion.header>
152
+
153
+ {/* Result */}
154
+ <div className="flex-1 overflow-y-auto p-4">
155
+ {result && (
156
+ <motion.div
157
+ initial={{ opacity: 0, y: 20 }}
158
+ animate={{ opacity: 1, y: 0 }}
159
+ className="space-y-4"
160
+ >
161
+ {/* Actions */}
162
+ <div className="flex gap-2">
163
+ <motion.button
164
+ whileHover={{ scale: 1.05 }}
165
+ whileTap={{ scale: 0.95 }}
166
+ className="flex items-center gap-2 px-4 py-2 bg-green-500/20 text-green-400 rounded-lg"
167
+ >
168
+ <Save className="w-4 h-4" />
169
+ Save Files
170
+ </motion.button>
171
+ <motion.button
172
+ whileHover={{ scale: 1.05 }}
173
+ whileTap={{ scale: 0.95 }}
174
+ className="flex items-center gap-2 px-4 py-2 bg-sky-500/20 text-sky-400 rounded-lg"
175
+ >
176
+ <MessageSquare className="w-4 h-4" />
177
+ Discuss
178
+ </motion.button>
179
+ </div>
180
+
181
+ {/* Generated Content */}
182
+ <div className="bg-slate-900/60 backdrop-blur-xl border border-white/10 rounded-2xl p-6">
183
+ <div className="flex items-center gap-2 mb-4 pb-4 border-b border-white/10">
184
+ <FileCode className="w-5 h-5 text-violet-400" />
185
+ <span className="font-medium">Generated Feature</span>
186
+ </div>
187
+ <div className="prose prose-invert max-w-none">
188
+ <pre className="whitespace-pre-wrap text-sm">{result}</pre>
189
+ </div>
190
+ </div>
191
+ </motion.div>
192
+ )}
193
+
194
+ {!result && !isGenerating && (
195
+ <motion.div
196
+ initial={{ opacity: 0 }}
197
+ animate={{ opacity: 1 }}
198
+ className="flex flex-col items-center justify-center h-64 text-slate-500"
199
+ >
200
+ <Sparkles className="w-12 h-12 mb-4 opacity-50" />
201
+ <p>Describe a feature above to generate code</p>
202
+ </motion.div>
203
+ )}
204
+ </div>
205
+ </div>
206
+ );
207
+ }
frontend/src/app/globals.css ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import "tailwindcss";
2
+
3
+ /* Theme tokens */
4
+ @theme {
5
+ --color-background: #050608;
6
+ --color-foreground: #E2E8F0;
7
+ --color-card: rgba(15, 23, 42, 0.6);
8
+ --color-primary: #38BDF8;
9
+ --color-secondary: #8B5CF6;
10
+ --color-muted: #94A3B8;
11
+ --color-border: rgba(255, 255, 255, 0.08);
12
+ --color-glow: rgba(56, 189, 248, 0.3);
13
+ }
14
+
15
+ body {
16
+ background: radial-gradient(circle at 10% 20%, rgba(13, 17, 28, 1) 0%, rgba(5, 6, 8, 1) 90%);
17
+ color: var(--color-foreground);
18
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
19
+ min-height: 100vh;
20
+ }
21
+
22
+ /* Scrollbar */
23
+ ::-webkit-scrollbar {
24
+ width: 8px;
25
+ height: 8px;
26
+ }
27
+
28
+ ::-webkit-scrollbar-track {
29
+ background: rgba(15, 23, 42, 0.3);
30
+ border-radius: 4px;
31
+ }
32
+
33
+ ::-webkit-scrollbar-thumb {
34
+ background: rgba(148, 163, 184, 0.3);
35
+ border-radius: 4px;
36
+ }
37
+
38
+ ::-webkit-scrollbar-thumb:hover {
39
+ background: rgba(148, 163, 184, 0.5);
40
+ }
41
+
42
+ /* Code blocks */
43
+ pre {
44
+ background: #0B0E14 !important;
45
+ border: 1px solid var(--color-border);
46
+ border-radius: 12px;
47
+ padding: 1rem;
48
+ overflow-x: auto;
49
+ }
50
+
51
+ code {
52
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
53
+ font-size: 0.9em;
54
+ }
frontend/src/app/layout.tsx ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { motion, AnimatePresence } from 'framer-motion';
4
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
5
+ import Link from 'next/link';
6
+ import { usePathname } from 'next/navigation';
7
+ import './globals.css';
8
+
9
+ const queryClient = new QueryClient();
10
+
11
+ export default function RootLayout({
12
+ children,
13
+ }: {
14
+ children: React.ReactNode;
15
+ }) {
16
+ return (
17
+ <html lang="en" className="dark">
18
+ <head>
19
+ <link
20
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
21
+ rel="stylesheet"
22
+ />
23
+ </head>
24
+ <body className="antialiased">
25
+ <QueryClientProvider client={queryClient}>
26
+ <div className="min-h-screen flex">
27
+ {/* Sidebar */}
28
+ <Sidebar />
29
+
30
+ {/* Main Content */}
31
+ <main className="flex-1 flex flex-col">
32
+ <AnimatePresence mode="wait">
33
+ <motion.div
34
+ key="content"
35
+ initial={{ opacity: 0, y: 20 }}
36
+ animate={{ opacity: 1, y: 0 }}
37
+ exit={{ opacity: 0, y: -20 }}
38
+ transition={{ duration: 0.3, ease: 'easeOut' }}
39
+ className="flex-1"
40
+ >
41
+ {children}
42
+ </motion.div>
43
+ </AnimatePresence>
44
+ </main>
45
+ </div>
46
+ </QueryClientProvider>
47
+ </body>
48
+ </html>
49
+ );
50
+ }
51
+
52
+ // Sidebar Component
53
+ function Sidebar() {
54
+ const pathname = usePathname();
55
+
56
+ return (
57
+ <motion.aside
58
+ initial={{ x: -100, opacity: 0 }}
59
+ animate={{ x: 0, opacity: 1 }}
60
+ transition={{ duration: 0.5, ease: 'easeOut' }}
61
+ className="w-64 bg-slate-900/60 backdrop-blur-xl border-r border-white/10 p-4 flex flex-col"
62
+ >
63
+ {/* Logo */}
64
+ <div className="flex items-center gap-3 mb-8 px-2">
65
+ <div className="w-10 h-10 rounded-xl bg-gradient-to-br from-sky-400 to-violet-500 flex items-center justify-center">
66
+ <span className="text-xl">🕷️</span>
67
+ </div>
68
+ <div>
69
+ <h1 className="font-bold text-lg bg-gradient-to-r from-sky-400 to-violet-400 bg-clip-text text-transparent">
70
+ Code Crawler
71
+ </h1>
72
+ <p className="text-xs text-slate-500">v2.0</p>
73
+ </div>
74
+ </div>
75
+
76
+ {/* Navigation */}
77
+ <nav className="flex-1 space-y-2">
78
+ <NavItem href="/" icon="📁" label="Upload" active={pathname === '/'} />
79
+ <NavItem href="/chat" icon="💬" label="Chat" active={pathname === '/chat'} />
80
+ <NavItem href="/search" icon="🔍" label="Search" active={pathname === '/search'} />
81
+ <NavItem href="/refactor" icon="🔧" label="Refactor" active={pathname === '/refactor'} />
82
+ <NavItem href="/generate" icon="✨" label="Generate" active={pathname === '/generate'} />
83
+ </nav>
84
+
85
+ {/* Footer */}
86
+ <div className="pt-4 border-t border-white/10">
87
+ <div className="px-4 py-2 text-xs text-slate-500">
88
+ Powered by Gemini AI
89
+ </div>
90
+ </div>
91
+ </motion.aside>
92
+ );
93
+ }
94
+
95
+ function NavItem({ href, icon, label, active }: { href: string; icon: string; label: string; active: boolean }) {
96
+ return (
97
+ <Link href={href}>
98
+ <motion.div
99
+ whileHover={{ scale: 1.02, x: 4 }}
100
+ whileTap={{ scale: 0.98 }}
101
+ className={`flex items-center gap-3 px-4 py-3 rounded-xl transition-all cursor-pointer ${active
102
+ ? 'bg-gradient-to-r from-sky-500/20 to-violet-500/20 border border-sky-500/30'
103
+ : 'hover:bg-white/5'
104
+ }`}
105
+ >
106
+ <span className="text-lg">{icon}</span>
107
+ <span className={`font-medium ${active ? 'text-sky-400' : 'text-slate-300'}`}>
108
+ {label}
109
+ </span>
110
+ </motion.div>
111
+ </Link>
112
+ );
113
+ }
frontend/src/app/page.tsx ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { motion } from 'framer-motion';
4
+ import { useState, useCallback } from 'react';
5
+ import { Upload, Folder, FileCode, ArrowRight, Loader2 } from 'lucide-react';
6
+
7
+ export default function HomePage() {
8
+ const [isDragging, setIsDragging] = useState(false);
9
+ const [isUploading, setIsUploading] = useState(false);
10
+ const [uploadProgress, setUploadProgress] = useState(0);
11
+
12
+ const handleDrop = useCallback((e: React.DragEvent) => {
13
+ e.preventDefault();
14
+ setIsDragging(false);
15
+
16
+ const files = e.dataTransfer.files;
17
+ if (files.length > 0) {
18
+ handleUpload(files[0]);
19
+ }
20
+ }, []);
21
+
22
+ const handleUpload = async (file: File) => {
23
+ setIsUploading(true);
24
+
25
+ // Simulate progress
26
+ for (let i = 0; i <= 100; i += 10) {
27
+ await new Promise(r => setTimeout(r, 200));
28
+ setUploadProgress(i);
29
+ }
30
+
31
+ // TODO: Actually upload to backend
32
+ setTimeout(() => {
33
+ setIsUploading(false);
34
+ window.location.href = '/chat';
35
+ }, 500);
36
+ };
37
+
38
+ return (
39
+ <div className="flex-1 flex flex-col items-center justify-center p-8">
40
+ {/* Hero Section */}
41
+ <motion.div
42
+ initial={{ opacity: 0, y: 30 }}
43
+ animate={{ opacity: 1, y: 0 }}
44
+ transition={{ duration: 0.6 }}
45
+ className="text-center mb-12"
46
+ >
47
+ <motion.div
48
+ initial={{ scale: 0 }}
49
+ animate={{ scale: 1 }}
50
+ transition={{ delay: 0.2, type: 'spring', stiffness: 200 }}
51
+ className="w-24 h-24 mx-auto mb-6 rounded-2xl bg-gradient-to-br from-sky-400 to-violet-500 flex items-center justify-center shadow-lg shadow-sky-500/30"
52
+ >
53
+ <span className="text-5xl">🕷️</span>
54
+ </motion.div>
55
+
56
+ <h1 className="text-4xl font-bold bg-gradient-to-r from-sky-400 to-violet-400 bg-clip-text text-transparent mb-4">
57
+ Code Crawler
58
+ </h1>
59
+ <p className="text-slate-400 text-lg max-w-md">
60
+ AI-powered codebase assistant. Upload your project and start exploring.
61
+ </p>
62
+ </motion.div>
63
+
64
+ {/* Upload Zone */}
65
+ <motion.div
66
+ initial={{ opacity: 0, scale: 0.95 }}
67
+ animate={{ opacity: 1, scale: 1 }}
68
+ transition={{ delay: 0.3, duration: 0.5 }}
69
+ onDragOver={(e) => { e.preventDefault(); setIsDragging(true); }}
70
+ onDragLeave={() => setIsDragging(false)}
71
+ onDrop={handleDrop}
72
+ className={`
73
+ w-full max-w-2xl p-12 rounded-2xl border-2 border-dashed
74
+ transition-all duration-300 cursor-pointer
75
+ ${isDragging
76
+ ? 'border-sky-400 bg-sky-500/10 scale-105'
77
+ : 'border-slate-600 hover:border-sky-500/50 hover:bg-slate-800/30'
78
+ }
79
+ `}
80
+ >
81
+ {isUploading ? (
82
+ <div className="text-center">
83
+ <Loader2 className="w-12 h-12 mx-auto mb-4 text-sky-400 animate-spin" />
84
+ <p className="text-slate-300 mb-4">Processing codebase...</p>
85
+ <div className="w-full bg-slate-700 rounded-full h-2">
86
+ <motion.div
87
+ className="bg-gradient-to-r from-sky-400 to-violet-500 h-2 rounded-full"
88
+ initial={{ width: 0 }}
89
+ animate={{ width: `${uploadProgress}%` }}
90
+ transition={{ duration: 0.3 }}
91
+ />
92
+ </div>
93
+ <p className="text-sm text-slate-500 mt-2">{uploadProgress}%</p>
94
+ </div>
95
+ ) : (
96
+ <div className="text-center">
97
+ <motion.div
98
+ animate={{ y: isDragging ? -10 : 0 }}
99
+ className="mb-6"
100
+ >
101
+ <Upload className="w-16 h-16 mx-auto text-slate-500" />
102
+ </motion.div>
103
+ <p className="text-xl text-slate-300 mb-2">
104
+ Drop your project here
105
+ </p>
106
+ <p className="text-slate-500 mb-6">
107
+ or click to select a ZIP file
108
+ </p>
109
+
110
+ <input
111
+ type="file"
112
+ accept=".zip"
113
+ onChange={(e) => e.target.files?.[0] && handleUpload(e.target.files[0])}
114
+ className="hidden"
115
+ id="file-input"
116
+ />
117
+ <label
118
+ htmlFor="file-input"
119
+ className="inline-flex items-center gap-2 px-6 py-3 bg-gradient-to-r from-sky-500 to-violet-500 rounded-xl font-semibold hover:opacity-90 transition-opacity cursor-pointer"
120
+ >
121
+ <Folder className="w-5 h-5" />
122
+ Browse Files
123
+ </label>
124
+ </div>
125
+ )}
126
+ </motion.div>
127
+
128
+ {/* Features */}
129
+ <motion.div
130
+ initial={{ opacity: 0 }}
131
+ animate={{ opacity: 1 }}
132
+ transition={{ delay: 0.5 }}
133
+ className="mt-12 flex gap-6"
134
+ >
135
+ {[
136
+ { icon: '💬', label: 'Chat' },
137
+ { icon: '🔍', label: 'Search' },
138
+ { icon: '🔧', label: 'Refactor' },
139
+ { icon: '✨', label: 'Generate' },
140
+ ].map((feature, i) => (
141
+ <motion.div
142
+ key={feature.label}
143
+ initial={{ opacity: 0, y: 20 }}
144
+ animate={{ opacity: 1, y: 0 }}
145
+ transition={{ delay: 0.6 + i * 0.1 }}
146
+ className="bg-slate-900/60 backdrop-blur-xl border border-white/10 rounded-2xl p-6 text-center min-w-[100px]"
147
+ >
148
+ <span className="text-2xl mb-2 block">{feature.icon}</span>
149
+ <span className="text-sm text-slate-400">{feature.label}</span>
150
+ </motion.div>
151
+ ))}
152
+ </motion.div>
153
+ </div>
154
+ );
155
+ }
frontend/src/app/refactor/page.tsx ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { motion } from 'framer-motion';
4
+ import { useState } from 'react';
5
+ import { Wrench, Play, Eye, CheckCircle, XCircle } from 'lucide-react';
6
+
7
+ export default function RefactorPage() {
8
+ const [searchPattern, setSearchPattern] = useState('');
9
+ const [replacePattern, setReplacePattern] = useState('');
10
+ const [filePattern, setFilePattern] = useState('**/*.py');
11
+ const [isDryRun, setIsDryRun] = useState(true);
12
+ const [isRefactoring, setIsRefactoring] = useState(false);
13
+ const [result, setResult] = useState<any>(null);
14
+
15
+ const commonPatterns = [
16
+ { name: 'print → logging', search: 'print\\((.*)\\)', replace: 'logger.info(\\1)' },
17
+ { name: 'assertEqual → assert', search: 'assertEqual\\(([^,]+),\\s*([^)]+)\\)', replace: 'assert \\1 == \\2' },
18
+ { name: 'Remove trailing whitespace', search: '[ \\t]+$', replace: '' },
19
+ ];
20
+
21
+ const handleRefactor = async (e: React.FormEvent) => {
22
+ e.preventDefault();
23
+ if (!searchPattern.trim() || !replacePattern) return;
24
+
25
+ setIsRefactoring(true);
26
+
27
+ try {
28
+ const response = await fetch('http://localhost:8000/api/refactor', {
29
+ method: 'POST',
30
+ headers: { 'Content-Type': 'application/json' },
31
+ body: JSON.stringify({
32
+ search_pattern: searchPattern,
33
+ replace_pattern: replacePattern,
34
+ file_pattern: filePattern,
35
+ dry_run: isDryRun,
36
+ }),
37
+ });
38
+
39
+ const data = await response.json();
40
+ setResult(data);
41
+ } catch (error) {
42
+ console.error('Refactor failed:', error);
43
+ } finally {
44
+ setIsRefactoring(false);
45
+ }
46
+ };
47
+
48
+ return (
49
+ <div className="flex flex-col h-screen">
50
+ {/* Header */}
51
+ <motion.header
52
+ initial={{ y: -20, opacity: 0 }}
53
+ animate={{ y: 0, opacity: 1 }}
54
+ className="bg-slate-900/60 backdrop-blur-xl border border-white/10 rounded-2xl p-6 m-4 mb-0"
55
+ >
56
+ <div className="flex items-center gap-3 mb-4">
57
+ <div className="w-10 h-10 rounded-xl bg-gradient-to-br from-orange-400 to-red-500 flex items-center justify-center">
58
+ 🔧
59
+ </div>
60
+ <div>
61
+ <h1 className="font-bold text-lg">Refactor Mode</h1>
62
+ <p className="text-sm text-slate-400">Automated code refactoring with regex</p>
63
+ </div>
64
+ </div>
65
+
66
+ {/* Common Patterns */}
67
+ <div className="flex gap-2 mb-4">
68
+ {commonPatterns.map((p) => (
69
+ <motion.button
70
+ key={p.name}
71
+ whileHover={{ scale: 1.05 }}
72
+ whileTap={{ scale: 0.95 }}
73
+ onClick={() => {
74
+ setSearchPattern(p.search);
75
+ setReplacePattern(p.replace);
76
+ }}
77
+ className="px-3 py-1.5 bg-slate-800 rounded-lg text-sm text-slate-300 hover:bg-slate-700 transition-colors"
78
+ >
79
+ {p.name}
80
+ </motion.button>
81
+ ))}
82
+ </div>
83
+
84
+ {/* Form */}
85
+ <form onSubmit={handleRefactor} className="space-y-4">
86
+ <div className="grid grid-cols-2 gap-4">
87
+ <div>
88
+ <label className="block text-sm text-slate-400 mb-2">Search Pattern</label>
89
+ <input
90
+ type="text"
91
+ value={searchPattern}
92
+ onChange={(e) => setSearchPattern(e.target.value)}
93
+ placeholder="e.g., print\((.*)\)"
94
+ className="w-full px-4 py-3 bg-slate-800/50 border border-slate-700 rounded-xl focus:border-orange-500 focus:outline-none transition-colors font-mono text-sm"
95
+ />
96
+ </div>
97
+ <div>
98
+ <label className="block text-sm text-slate-400 mb-2">Replace Pattern</label>
99
+ <input
100
+ type="text"
101
+ value={replacePattern}
102
+ onChange={(e) => setReplacePattern(e.target.value)}
103
+ placeholder="e.g., logger.info(\1)"
104
+ className="w-full px-4 py-3 bg-slate-800/50 border border-slate-700 rounded-xl focus:border-orange-500 focus:outline-none transition-colors font-mono text-sm"
105
+ />
106
+ </div>
107
+ </div>
108
+
109
+ <div className="flex gap-4 items-end">
110
+ <div className="flex-1">
111
+ <label className="block text-sm text-slate-400 mb-2">File Pattern</label>
112
+ <input
113
+ type="text"
114
+ value={filePattern}
115
+ onChange={(e) => setFilePattern(e.target.value)}
116
+ className="w-full px-4 py-3 bg-slate-800/50 border border-slate-700 rounded-xl focus:border-orange-500 focus:outline-none transition-colors"
117
+ />
118
+ </div>
119
+
120
+ <label className="flex items-center gap-2 px-4 py-3 bg-slate-800/50 rounded-xl cursor-pointer">
121
+ <input
122
+ type="checkbox"
123
+ checked={isDryRun}
124
+ onChange={(e) => setIsDryRun(e.target.checked)}
125
+ className="w-4 h-4 accent-orange-500"
126
+ />
127
+ <span className="text-sm text-slate-300">Dry Run</span>
128
+ <Eye className="w-4 h-4 text-slate-400" />
129
+ </label>
130
+ </div>
131
+
132
+ <motion.button
133
+ whileHover={{ scale: 1.02 }}
134
+ whileTap={{ scale: 0.98 }}
135
+ type="submit"
136
+ disabled={isRefactoring || !searchPattern.trim()}
137
+ className="w-full py-3 bg-gradient-to-r from-orange-500 to-red-500 rounded-xl font-semibold flex items-center justify-center gap-2 disabled:opacity-50"
138
+ >
139
+ {isRefactoring ? (
140
+ <div className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
141
+ ) : (
142
+ <>
143
+ <Wrench className="w-5 h-5" />
144
+ {isDryRun ? 'Preview Changes' : 'Apply Refactoring'}
145
+ </>
146
+ )}
147
+ </motion.button>
148
+ </form>
149
+ </motion.header>
150
+
151
+ {/* Results */}
152
+ <div className="flex-1 overflow-y-auto p-4">
153
+ {result && (
154
+ <motion.div
155
+ initial={{ opacity: 0, y: 20 }}
156
+ animate={{ opacity: 1, y: 0 }}
157
+ className="space-y-4"
158
+ >
159
+ {/* Summary */}
160
+ <div className={`bg-slate-900/60 backdrop-blur-xl border border-white/10 rounded-2xl p-6 flex items-center gap-4 ${result.success ? 'border-green-500/30' : 'border-red-500/30'
161
+ }`}>
162
+ {result.success ? (
163
+ <CheckCircle className="w-8 h-8 text-green-400" />
164
+ ) : (
165
+ <XCircle className="w-8 h-8 text-red-400" />
166
+ )}
167
+ <div>
168
+ <p className="font-semibold">
169
+ {result.success
170
+ ? `${isDryRun ? 'Preview:' : 'Applied:'} ${result.files_changed || 0} files, ${result.total_replacements || 0} replacements`
171
+ : 'Refactoring failed'
172
+ }
173
+ </p>
174
+ {result.error && (
175
+ <p className="text-sm text-red-400">{result.error}</p>
176
+ )}
177
+ </div>
178
+ </div>
179
+
180
+ {/* Changes */}
181
+ {result.changes?.map((change: any, i: number) => (
182
+ <motion.div
183
+ key={i}
184
+ initial={{ opacity: 0, x: -20 }}
185
+ animate={{ opacity: 1, x: 0 }}
186
+ transition={{ delay: i * 0.05 }}
187
+ className="bg-slate-900/60 backdrop-blur-xl border border-white/10 rounded-2xl p-6"
188
+ >
189
+ <p className="text-sm font-medium text-slate-300 mb-2">
190
+ 📄 {change.file_path} ({change.replacements} changes)
191
+ </p>
192
+ {change.preview && (
193
+ <pre className="text-xs overflow-x-auto">{change.preview}</pre>
194
+ )}
195
+ </motion.div>
196
+ ))}
197
+ </motion.div>
198
+ )}
199
+
200
+ {!result && !isRefactoring && (
201
+ <motion.div
202
+ initial={{ opacity: 0 }}
203
+ animate={{ opacity: 1 }}
204
+ className="flex flex-col items-center justify-center h-64 text-slate-500"
205
+ >
206
+ <Wrench className="w-12 h-12 mb-4 opacity-50" />
207
+ <p>Configure patterns above to preview or apply refactoring</p>
208
+ </motion.div>
209
+ )}
210
+ </div>
211
+ </div>
212
+ );
213
+ }
frontend/src/app/search/page.tsx ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { motion, AnimatePresence } from 'framer-motion';
4
+ import { useState } from 'react';
5
+ import { Search, FileCode, ChevronDown, ChevronRight } from 'lucide-react';
6
+
7
+ interface SearchResult {
8
+ file_path: string;
9
+ line_number: number;
10
+ line_content: string;
11
+ context_before: string[];
12
+ context_after: string[];
13
+ }
14
+
15
+ export default function SearchPage() {
16
+ const [pattern, setPattern] = useState('');
17
+ const [filePattern, setFilePattern] = useState('**/*.py');
18
+ const [results, setResults] = useState<SearchResult[]>([]);
19
+ const [isSearching, setIsSearching] = useState(false);
20
+ const [expandedResults, setExpandedResults] = useState<Set<number>>(new Set());
21
+
22
+ const handleSearch = async (e: React.FormEvent) => {
23
+ e.preventDefault();
24
+ if (!pattern.trim()) return;
25
+
26
+ setIsSearching(true);
27
+
28
+ try {
29
+ const response = await fetch('http://localhost:8000/api/search', {
30
+ method: 'POST',
31
+ headers: { 'Content-Type': 'application/json' },
32
+ body: JSON.stringify({
33
+ pattern,
34
+ file_pattern: filePattern,
35
+ context_lines: 2
36
+ }),
37
+ });
38
+
39
+ const data = await response.json();
40
+ setResults(data.results || []);
41
+ } catch (error) {
42
+ console.error('Search failed:', error);
43
+ } finally {
44
+ setIsSearching(false);
45
+ }
46
+ };
47
+
48
+ const toggleExpand = (index: number) => {
49
+ const newExpanded = new Set(expandedResults);
50
+ if (newExpanded.has(index)) {
51
+ newExpanded.delete(index);
52
+ } else {
53
+ newExpanded.add(index);
54
+ }
55
+ setExpandedResults(newExpanded);
56
+ };
57
+
58
+ return (
59
+ <div className="flex flex-col h-screen">
60
+ {/* Header */}
61
+ <motion.header
62
+ initial={{ y: -20, opacity: 0 }}
63
+ animate={{ y: 0, opacity: 1 }}
64
+ className="bg-slate-900/60 backdrop-blur-xl border border-white/10 rounded-2xl p-6 m-4 mb-0"
65
+ >
66
+ <div className="flex items-center gap-3 mb-4">
67
+ <div className="w-10 h-10 rounded-xl bg-gradient-to-br from-emerald-400 to-cyan-500 flex items-center justify-center">
68
+ 🔍
69
+ </div>
70
+ <div>
71
+ <h1 className="font-bold text-lg">Search Mode</h1>
72
+ <p className="text-sm text-slate-400">Find patterns across your codebase</p>
73
+ </div>
74
+ </div>
75
+
76
+ {/* Search Form */}
77
+ <form onSubmit={handleSearch} className="space-y-4">
78
+ <div className="flex gap-4">
79
+ <div className="flex-1">
80
+ <label className="block text-sm text-slate-400 mb-2">Search Pattern (regex)</label>
81
+ <input
82
+ type="text"
83
+ value={pattern}
84
+ onChange={(e) => setPattern(e.target.value)}
85
+ placeholder="e.g., class\s+(\w+) or def.*login"
86
+ className="w-full px-4 py-3 bg-slate-800/50 border border-slate-700 rounded-xl focus:border-sky-500 focus:outline-none transition-colors"
87
+ />
88
+ </div>
89
+ <div className="w-48">
90
+ <label className="block text-sm text-slate-400 mb-2">File Pattern</label>
91
+ <input
92
+ type="text"
93
+ value={filePattern}
94
+ onChange={(e) => setFilePattern(e.target.value)}
95
+ placeholder="**/*.py"
96
+ className="w-full px-4 py-3 bg-slate-800/50 border border-slate-700 rounded-xl focus:border-sky-500 focus:outline-none transition-colors"
97
+ />
98
+ </div>
99
+ </div>
100
+
101
+ <motion.button
102
+ whileHover={{ scale: 1.02 }}
103
+ whileTap={{ scale: 0.98 }}
104
+ type="submit"
105
+ disabled={isSearching || !pattern.trim()}
106
+ className="w-full py-3 bg-gradient-to-r from-emerald-500 to-cyan-500 rounded-xl font-semibold flex items-center justify-center gap-2 disabled:opacity-50"
107
+ >
108
+ {isSearching ? (
109
+ <div className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
110
+ ) : (
111
+ <>
112
+ <Search className="w-5 h-5" />
113
+ Search Codebase
114
+ </>
115
+ )}
116
+ </motion.button>
117
+ </form>
118
+ </motion.header>
119
+
120
+ {/* Results */}
121
+ <div className="flex-1 overflow-y-auto p-4 space-y-3">
122
+ {results.length > 0 && (
123
+ <motion.p
124
+ initial={{ opacity: 0 }}
125
+ animate={{ opacity: 1 }}
126
+ className="text-sm text-slate-400 mb-4"
127
+ >
128
+ Found {results.length} matches
129
+ </motion.p>
130
+ )}
131
+
132
+ <AnimatePresence>
133
+ {results.map((result, i) => (
134
+ <motion.div
135
+ key={i}
136
+ initial={{ opacity: 0, y: 20 }}
137
+ animate={{ opacity: 1, y: 0 }}
138
+ transition={{ delay: i * 0.03 }}
139
+ className="bg-slate-900/60 backdrop-blur-xl border border-white/10 rounded-2xl p-6 !p-0 overflow-hidden"
140
+ >
141
+ {/* Header */}
142
+ <button
143
+ onClick={() => toggleExpand(i)}
144
+ className="w-full flex items-center gap-3 p-4 hover:bg-white/5 transition-colors"
145
+ >
146
+ {expandedResults.has(i) ? (
147
+ <ChevronDown className="w-4 h-4 text-slate-400" />
148
+ ) : (
149
+ <ChevronRight className="w-4 h-4 text-slate-400" />
150
+ )}
151
+ <FileCode className="w-4 h-4 text-sky-400" />
152
+ <span className="text-sm font-medium text-slate-300">
153
+ {result.file_path}
154
+ </span>
155
+ <span className="text-xs text-slate-500">
156
+ Line {result.line_number}
157
+ </span>
158
+ </button>
159
+
160
+ {/* Code Preview */}
161
+ <AnimatePresence>
162
+ {expandedResults.has(i) && (
163
+ <motion.div
164
+ initial={{ height: 0, opacity: 0 }}
165
+ animate={{ height: 'auto', opacity: 1 }}
166
+ exit={{ height: 0, opacity: 0 }}
167
+ className="border-t border-slate-700"
168
+ >
169
+ <pre className="p-4 text-sm overflow-x-auto !rounded-none !border-0">
170
+ {result.context_before?.map((line, j) => (
171
+ <div key={`before-${j}`} className="text-slate-500">{line}</div>
172
+ ))}
173
+ <div className="bg-sky-500/20 text-sky-200 -mx-4 px-4 py-1 border-l-2 border-sky-400">
174
+ {result.line_content}
175
+ </div>
176
+ {result.context_after?.map((line, j) => (
177
+ <div key={`after-${j}`} className="text-slate-500">{line}</div>
178
+ ))}
179
+ </pre>
180
+ </motion.div>
181
+ )}
182
+ </AnimatePresence>
183
+ </motion.div>
184
+ ))}
185
+ </AnimatePresence>
186
+
187
+ {results.length === 0 && !isSearching && (
188
+ <motion.div
189
+ initial={{ opacity: 0 }}
190
+ animate={{ opacity: 1 }}
191
+ className="flex flex-col items-center justify-center h-64 text-slate-500"
192
+ >
193
+ <Search className="w-12 h-12 mb-4 opacity-50" />
194
+ <p>Enter a pattern to search your codebase</p>
195
+ </motion.div>
196
+ )}
197
+ </div>
198
+ </div>
199
+ );
200
+ }
frontend/tsconfig.json ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "react-jsx",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./src/*"]
23
+ }
24
+ },
25
+ "include": [
26
+ "next-env.d.ts",
27
+ "**/*.ts",
28
+ "**/*.tsx",
29
+ ".next/types/**/*.ts",
30
+ ".next/dev/types/**/*.ts",
31
+ "**/*.mts"
32
+ ],
33
+ "exclude": ["node_modules"]
34
+ }