wuhp commited on
Commit
96beddd
·
verified ·
1 Parent(s): 02da259

Update components/CodeViewer.tsx

Browse files
Files changed (1) hide show
  1. components/CodeViewer.tsx +275 -14
components/CodeViewer.tsx CHANGED
@@ -1,6 +1,21 @@
1
- import React, { useState, useEffect } from 'react';
2
- import { X, Copy, Check, Terminal, HardHat, Search, Wand2, CheckCircle2, ArrowLeft, Play } from 'lucide-react';
3
- import { generateCodeWithAgents, AgentStatus } from '../services/geminiService';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
  interface CodeViewerProps {
6
  code: string; // This represents the PROMPT text initially
@@ -17,15 +32,77 @@ const CodeViewer: React.FC<CodeViewerProps> = ({ code, isOpen, onClose, isLoadin
17
  const [agentStatus, setAgentStatus] = useState<AgentStatus>('idle');
18
  const [agentMessage, setAgentMessage] = useState('');
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  // Reset state when opening/closing or changing input
21
  useEffect(() => {
22
  if (isOpen) {
23
  setViewMode('prompt');
24
  setFinalCode('');
25
  setAgentStatus('idle');
 
 
 
 
 
26
  }
27
  }, [isOpen, code]);
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  const handleCopy = (text: string) => {
30
  navigator.clipboard.writeText(text);
31
  setCopied(true);
@@ -42,7 +119,40 @@ const CodeViewer: React.FC<CodeViewerProps> = ({ code, isOpen, onClose, isLoadin
42
  setAgentStatus(status);
43
  setAgentMessage(msg);
44
  });
45
- setFinalCode(result);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  // Small delay to show completion state
47
  setTimeout(() => {
48
  setViewMode('code');
@@ -55,7 +165,7 @@ const CodeViewer: React.FC<CodeViewerProps> = ({ code, isOpen, onClose, isLoadin
55
  };
56
 
57
  const getStepStatus = (step: AgentStatus, current: AgentStatus) => {
58
- const order = ['idle', 'architect', 'critic', 'refiner', 'complete'];
59
  const stepIdx = order.indexOf(step);
60
  const currentIdx = order.indexOf(current);
61
 
@@ -113,13 +223,24 @@ const CodeViewer: React.FC<CodeViewerProps> = ({ code, isOpen, onClose, isLoadin
113
  )}
114
 
115
  {viewMode !== 'building' && (
116
- <button
117
- onClick={() => handleCopy(viewMode === 'code' ? finalCode : code)}
118
- className="flex items-center gap-2 px-3 py-1.5 bg-slate-800 hover:bg-slate-700 rounded text-xs font-medium text-slate-300 transition-colors border border-slate-700"
119
- >
120
- {copied ? <Check size={14} className="text-emerald-400"/> : <Copy size={14} />}
121
- {copied ? 'Copied' : 'Copy'}
122
- </button>
 
 
 
 
 
 
 
 
 
 
 
123
  )}
124
 
125
  {viewMode === 'prompt' && !isLoading && (
@@ -163,6 +284,7 @@ const CodeViewer: React.FC<CodeViewerProps> = ({ code, isOpen, onClose, isLoadin
163
  {renderAgentStep('architect', <HardHat size={24} />, "Coder Agent: Writing Implementation")}
164
  {renderAgentStep('critic', <Search size={24} />, "Reviewer Agent: Analyzing Logic")}
165
  {renderAgentStep('refiner', <Wand2 size={24} />, "Polisher Agent: Finalizing Code")}
 
166
  </div>
167
  {agentStatus === 'complete' && (
168
  <div className="mt-8 text-emerald-400 font-bold animate-bounce">
@@ -174,8 +296,147 @@ const CodeViewer: React.FC<CodeViewerProps> = ({ code, isOpen, onClose, isLoadin
174
 
175
  {/* View: Final Code */}
176
  {viewMode === 'code' && (
177
- <div className="h-full overflow-auto p-6 font-mono text-sm leading-relaxed whitespace-pre-wrap text-blue-100 selection:bg-blue-500/30">
178
- {finalCode}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  </div>
180
  )}
181
 
 
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { X, Copy, Check, Terminal, HardHat, Search, Wand2, CheckCircle2, ArrowLeft, Play, Send, MessageSquare, Sparkles, Loader2, History, FileCode, Plus, Minus } from 'lucide-react';
3
+ import { generateCodeWithAgents, AgentStatus, generateCodePatches, applyPatches, DEFAULT_MODEL } from '../services/geminiService';
4
+
5
+ interface Message {
6
+ role: 'user' | 'assistant';
7
+ content: string;
8
+ summary?: string;
9
+ files_changed?: string[];
10
+ }
11
+
12
+ interface PatchHistory {
13
+ id: string;
14
+ timestamp: Date;
15
+ summary: string;
16
+ files_changed: string[];
17
+ patches: { search: string, replace: string }[];
18
+ }
19
 
20
  interface CodeViewerProps {
21
  code: string; // This represents the PROMPT text initially
 
32
  const [agentStatus, setAgentStatus] = useState<AgentStatus>('idle');
33
  const [agentMessage, setAgentMessage] = useState('');
34
 
35
+ // Chat State
36
+ const [messages, setMessages] = useState<Message[]>([]);
37
+ const [inputValue, setInputValue] = useState('');
38
+ const [isChatting, setIsChatting] = useState(false);
39
+ const chatEndRef = useRef<HTMLDivElement>(null);
40
+
41
+ // History & Diff State
42
+ const [patchHistory, setPatchHistory] = useState<PatchHistory[]>([]);
43
+ const [isHistoryOpen, setIsHistoryOpen] = useState(false);
44
+ const [selectedHistoryId, setSelectedHistoryId] = useState<string | null>(null);
45
+
46
+ const scrollToBottom = () => {
47
+ chatEndRef.current?.scrollIntoView({ behavior: "smooth" });
48
+ };
49
+
50
+ useEffect(() => {
51
+ scrollToBottom();
52
+ }, [messages]);
53
+
54
  // Reset state when opening/closing or changing input
55
  useEffect(() => {
56
  if (isOpen) {
57
  setViewMode('prompt');
58
  setFinalCode('');
59
  setAgentStatus('idle');
60
+ setMessages([]);
61
+ setIsChatting(false);
62
+ setPatchHistory([]);
63
+ setIsHistoryOpen(false);
64
+ setSelectedHistoryId(null);
65
  }
66
  }, [isOpen, code]);
67
 
68
+ const handleSendMessage = async () => {
69
+ if (!inputValue.trim() || isChatting) return;
70
+
71
+ const userMsg = inputValue.trim();
72
+ setInputValue('');
73
+ setMessages(prev => [...prev, { role: 'user', content: userMsg }]);
74
+ setIsChatting(true);
75
+
76
+ try {
77
+ const patchResult = await generateCodePatches(finalCode, userMsg, DEFAULT_MODEL);
78
+ const updatedCode = applyPatches(finalCode, patchResult.patches);
79
+
80
+ const newPatch: PatchHistory = {
81
+ id: Date.now().toString(),
82
+ timestamp: new Date(),
83
+ summary: patchResult.summary,
84
+ files_changed: patchResult.files_changed,
85
+ patches: patchResult.patches
86
+ };
87
+
88
+ setPatchHistory(prev => [newPatch, ...prev]);
89
+ setFinalCode(updatedCode);
90
+ setMessages(prev => [...prev, {
91
+ role: 'assistant',
92
+ content: patchResult.reasoning || "I've applied the requested changes to the code.",
93
+ summary: patchResult.summary,
94
+ files_changed: patchResult.files_changed
95
+ }]);
96
+ } catch (error) {
97
+ setMessages(prev => [...prev, {
98
+ role: 'assistant',
99
+ content: "Sorry, I encountered an error while trying to apply those changes."
100
+ }]);
101
+ } finally {
102
+ setIsChatting(false);
103
+ }
104
+ };
105
+
106
  const handleCopy = (text: string) => {
107
  navigator.clipboard.writeText(text);
108
  setCopied(true);
 
119
  setAgentStatus(status);
120
  setAgentMessage(msg);
121
  });
122
+ setFinalCode(result.code);
123
+
124
+ // Add initial evaluation/patches to chat
125
+ if (result.evaluation) {
126
+ const initialMsgs: Message[] = [
127
+ {
128
+ role: 'assistant',
129
+ content: `I've completed the initial build. Here's my self-evaluation:
130
+ - Score: ${result.evaluation.score}/100
131
+ - Missing Components: ${result.evaluation.missing_components.length > 0 ? result.evaluation.missing_components.join(', ') : 'None'}
132
+ - Potential Bugs: ${result.evaluation.bugs.length > 0 ? result.evaluation.bugs.join(', ') : 'None'}`
133
+ }
134
+ ];
135
+
136
+ if (result.initialPatches) {
137
+ const patchHistoryItem: PatchHistory = {
138
+ id: 'initial-auto-fix',
139
+ timestamp: new Date(),
140
+ summary: result.initialPatches.summary,
141
+ files_changed: result.initialPatches.files_changed,
142
+ patches: result.initialPatches.patches
143
+ };
144
+ setPatchHistory([patchHistoryItem]);
145
+
146
+ initialMsgs.push({
147
+ role: 'assistant',
148
+ content: `I've automatically applied some improvements to address the evaluation findings.`,
149
+ summary: result.initialPatches.summary,
150
+ files_changed: result.initialPatches.files_changed
151
+ });
152
+ }
153
+ setMessages(initialMsgs);
154
+ }
155
+
156
  // Small delay to show completion state
157
  setTimeout(() => {
158
  setViewMode('code');
 
165
  };
166
 
167
  const getStepStatus = (step: AgentStatus, current: AgentStatus) => {
168
+ const order = ['idle', 'architect', 'critic', 'refiner', 'evaluator', 'complete'];
169
  const stepIdx = order.indexOf(step);
170
  const currentIdx = order.indexOf(current);
171
 
 
223
  )}
224
 
225
  {viewMode !== 'building' && (
226
+ <div className="flex items-center gap-2">
227
+ {viewMode === 'code' && (
228
+ <button
229
+ onClick={() => setIsHistoryOpen(!isHistoryOpen)}
230
+ className={`flex items-center gap-2 px-3 py-1.5 rounded text-xs font-medium transition-colors border ${isHistoryOpen ? 'bg-blue-600 border-blue-500 text-white' : 'bg-slate-800 hover:bg-slate-700 text-slate-300 border-slate-700'}`}
231
+ >
232
+ <History size={14} />
233
+ Change Logs
234
+ </button>
235
+ )}
236
+ <button
237
+ onClick={() => handleCopy(viewMode === 'code' ? finalCode : code)}
238
+ className="flex items-center gap-2 px-3 py-1.5 bg-slate-800 hover:bg-slate-700 rounded text-xs font-medium text-slate-300 transition-colors border border-slate-700"
239
+ >
240
+ {copied ? <Check size={14} className="text-emerald-400"/> : <Copy size={14} />}
241
+ {copied ? 'Copied' : 'Copy'}
242
+ </button>
243
+ </div>
244
  )}
245
 
246
  {viewMode === 'prompt' && !isLoading && (
 
284
  {renderAgentStep('architect', <HardHat size={24} />, "Coder Agent: Writing Implementation")}
285
  {renderAgentStep('critic', <Search size={24} />, "Reviewer Agent: Analyzing Logic")}
286
  {renderAgentStep('refiner', <Wand2 size={24} />, "Polisher Agent: Finalizing Code")}
287
+ {renderAgentStep('evaluator', <CheckCircle2 size={24} />, "Evaluator Agent: Verifying Requirements")}
288
  </div>
289
  {agentStatus === 'complete' && (
290
  <div className="mt-8 text-emerald-400 font-bold animate-bounce">
 
296
 
297
  {/* View: Final Code */}
298
  {viewMode === 'code' && (
299
+ <div className="flex h-full overflow-hidden">
300
+ <div className="flex-1 overflow-auto p-6 font-mono text-sm leading-relaxed whitespace-pre-wrap text-blue-100 selection:bg-blue-500/30">
301
+ {finalCode}
302
+ </div>
303
+
304
+ {/* History/Diff Overlay */}
305
+ {isHistoryOpen && (
306
+ <div className="absolute inset-0 z-40 bg-slate-950 flex flex-col animate-in slide-in-from-right duration-300">
307
+ <div className="flex items-center justify-between p-4 border-b border-slate-800 bg-slate-900">
308
+ <div className="flex items-center gap-3">
309
+ <History size={18} className="text-blue-400" />
310
+ <h3 className="font-bold text-slate-200">Change Logs</h3>
311
+ </div>
312
+ <button onClick={() => setIsHistoryOpen(false)} className="text-slate-400 hover:text-white">
313
+ <X size={20} />
314
+ </button>
315
+ </div>
316
+
317
+ <div className="flex-1 flex overflow-hidden">
318
+ {/* History List */}
319
+ <div className="w-64 border-r border-slate-800 overflow-y-auto p-2 space-y-2 bg-slate-900/50">
320
+ {patchHistory.length === 0 ? (
321
+ <div className="text-center py-10 text-slate-500 text-xs">No changes yet.</div>
322
+ ) : (
323
+ patchHistory.map((h) => (
324
+ <button
325
+ key={h.id}
326
+ onClick={() => setSelectedHistoryId(h.id)}
327
+ className={`w-full text-left p-3 rounded-lg border transition-all ${selectedHistoryId === h.id ? 'bg-blue-600/20 border-blue-500/50' : 'bg-slate-800/50 border-slate-700 hover:bg-slate-800'}`}
328
+ >
329
+ <div className="text-xs font-bold text-slate-200 truncate">{h.summary}</div>
330
+ <div className="text-[10px] text-slate-500 mt-1">{h.timestamp.toLocaleTimeString()}</div>
331
+ </button>
332
+ ))
333
+ )}
334
+ </div>
335
+
336
+ {/* Diff Viewer */}
337
+ <div className="flex-1 overflow-y-auto p-6 bg-[#0d1117]">
338
+ {selectedHistoryId ? (
339
+ <div className="space-y-6">
340
+ {patchHistory.find(h => h.id === selectedHistoryId)?.patches.map((p, i) => (
341
+ <div key={i} className="border border-slate-800 rounded-lg overflow-hidden">
342
+ <div className="bg-slate-800 px-4 py-2 text-xs font-mono text-slate-300 border-b border-slate-700 flex items-center gap-2">
343
+ <FileCode size={14} />
344
+ Patch {i + 1}
345
+ </div>
346
+ <div className="font-mono text-xs overflow-x-auto">
347
+ <div className="bg-red-900/20 p-4 border-b border-red-900/30 relative">
348
+ <Minus size={12} className="absolute left-1 top-4 text-red-500" />
349
+ <div className="text-red-200 opacity-70 line-through whitespace-pre">{p.search}</div>
350
+ </div>
351
+ <div className="bg-emerald-900/20 p-4 relative">
352
+ <Plus size={12} className="absolute left-1 top-4 text-emerald-500" />
353
+ <div className="text-emerald-200 whitespace-pre">{p.replace}</div>
354
+ </div>
355
+ </div>
356
+ </div>
357
+ ))}
358
+ </div>
359
+ ) : (
360
+ <div className="h-full flex flex-col items-center justify-center text-slate-500">
361
+ <History size={48} className="opacity-20 mb-4" />
362
+ <p>Select a change log to view the diff.</p>
363
+ </div>
364
+ )}
365
+ </div>
366
+ </div>
367
+ </div>
368
+ )}
369
+
370
+ {/* Chat Sidebar */}
371
+ <div className="w-80 border-l border-slate-800 bg-slate-900/50 flex flex-col">
372
+ <div className="p-4 border-b border-slate-800 flex items-center gap-2 text-slate-300 font-semibold text-sm">
373
+ <MessageSquare size={16} className="text-blue-400" />
374
+ AI Assistant
375
+ </div>
376
+
377
+ <div className="flex-1 overflow-y-auto p-4 space-y-4 scrollbar-thin scrollbar-thumb-slate-800">
378
+ {messages.length === 0 && (
379
+ <div className="text-center py-8">
380
+ <Sparkles className="mx-auto text-slate-700 mb-2" size={32} />
381
+ <p className="text-xs text-slate-500">Ask me to modify the code or add features.</p>
382
+ </div>
383
+ )}
384
+ {messages.map((msg, i) => (
385
+ <div key={i} className={`flex flex-col ${msg.role === 'user' ? 'items-end' : 'items-start'}`}>
386
+ <div className={`max-w-[90%] p-3 rounded-lg text-xs leading-relaxed ${
387
+ msg.role === 'user'
388
+ ? 'bg-blue-600 text-white rounded-tr-none'
389
+ : 'bg-slate-800 text-slate-300 rounded-tl-none border border-slate-700'
390
+ }`}>
391
+ {msg.content}
392
+
393
+ {msg.role === 'assistant' && msg.summary && (
394
+ <div className="mt-3 pt-3 border-t border-slate-700 space-y-2">
395
+ <div className="flex items-center gap-2 text-[10px] font-bold text-emerald-400 uppercase tracking-wider">
396
+ <CheckCircle2 size={10} />
397
+ Update Complete
398
+ </div>
399
+ <div className="text-slate-200 font-medium">{msg.summary}</div>
400
+ {msg.files_changed && msg.files_changed.length > 0 && (
401
+ <div className="flex items-center gap-1.5 text-[10px] text-slate-500">
402
+ <FileCode size={10} />
403
+ {msg.files_changed.length} file{msg.files_changed.length > 1 ? 's' : ''} changed
404
+ </div>
405
+ )}
406
+ </div>
407
+ )}
408
+ </div>
409
+ </div>
410
+ ))}
411
+ {isChatting && (
412
+ <div className="flex items-center gap-2 text-slate-500 text-xs animate-pulse">
413
+ <Loader2 size={12} className="animate-spin" />
414
+ Applying patches...
415
+ </div>
416
+ )}
417
+ <div ref={chatEndRef} />
418
+ </div>
419
+
420
+ <div className="p-4 border-t border-slate-800">
421
+ <div className="relative">
422
+ <input
423
+ type="text"
424
+ value={inputValue}
425
+ onChange={(e) => setInputValue(e.target.value)}
426
+ onKeyDown={(e) => e.key === 'Enter' && handleSendMessage()}
427
+ placeholder="Request changes..."
428
+ className="w-full bg-slate-950 border border-slate-700 rounded-lg pl-3 pr-10 py-2 text-xs text-slate-200 focus:outline-none focus:border-blue-500 transition-colors"
429
+ />
430
+ <button
431
+ onClick={handleSendMessage}
432
+ disabled={!inputValue.trim() || isChatting}
433
+ className="absolute right-2 top-1/2 -translate-y-1/2 text-slate-500 hover:text-blue-400 disabled:opacity-50 disabled:hover:text-slate-500 transition-colors"
434
+ >
435
+ <Send size={16} />
436
+ </button>
437
+ </div>
438
+ </div>
439
+ </div>
440
  </div>
441
  )}
442