Mayank2027 commited on
Commit
274b2ee
·
verified ·
1 Parent(s): 6666541

Delete frontend/src

Browse files
frontend/src/App.tsx DELETED
@@ -1,94 +0,0 @@
1
- import React, { useState, useEffect } from 'react';
2
- import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
3
- import ChatPanel from './components/ChatPanel';
4
- import AgentPanel from './components/AgentPanel';
5
- import CodeEditor from './components/CodeEditor';
6
- import TerminalTabs from './components/TerminalTabs';
7
- import FileTree from './components/FileTree';
8
- import ModelSelector from './components/ModelSelector';
9
- import TopBar from './components/TopBar';
10
- import StatusBar from './components/StatusBar';
11
- import GitPanel from './components/GitPanel';
12
- import BackgroundTasks from './components/BackgroundTasks';
13
- import { FaComments, FaRobot, FaTerminal, FaCodeBranch, FaTasks } from 'react-icons/fa';
14
-
15
- const App: React.FC = () => {
16
- const [activeBottom, setActiveBottom] = useState('chat');
17
- const [projectPath] = useState('/app/sandbox');
18
- const [activeFile, setActiveFile] = useState<string | null>(null);
19
- const [selectedModel, setSelectedModel] = useState('groq-llama-3.3-70b-versatile');
20
- const [sidebarVisible, setSidebarVisible] = useState(true);
21
-
22
- useEffect(() => {
23
- const handleKeyDown = (e: KeyboardEvent) => {
24
- const mod = e.ctrlKey || e.metaKey;
25
- if (mod && e.key === 'b') { e.preventDefault(); setSidebarVisible(prev => !prev); }
26
- else if (mod && e.key === '1') { e.preventDefault(); setActiveBottom('chat'); }
27
- else if (mod && e.key === '2') { e.preventDefault(); setActiveBottom('agent'); }
28
- else if (mod && e.key === '3') { e.preventDefault(); setActiveBottom('terminal'); }
29
- else if (mod && e.key === '4') { e.preventDefault(); setActiveBottom('git'); }
30
- else if (mod && e.key === '5') { e.preventDefault(); setActiveBottom('tasks'); }
31
- };
32
- window.addEventListener('keydown', handleKeyDown);
33
- return () => window.removeEventListener('keydown', handleKeyDown);
34
- }, []);
35
-
36
- return (
37
- <div style={{ display: 'flex', flexDirection: 'column', height: '100vh', background: 'var(--bg-primary)' }}>
38
- <TopBar />
39
- <div style={{ display: 'flex', flex: 1, overflow: 'hidden' }}>
40
- {sidebarVisible && (
41
- <div style={{ width: 280, background: 'var(--bg-secondary)', borderRight: '1px solid var(--border)', display: 'flex', flexDirection: 'column' }}>
42
- <div style={{ padding: '8px 12px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
43
- <span style={{ fontWeight: 600, fontSize: 13 }}>EXPLORER</span>
44
- <span onClick={() => setSidebarVisible(false)} style={{ cursor: 'pointer', color: 'var(--text-secondary)' }}>×</span>
45
- </div>
46
- <ModelSelector selectedModel={selectedModel} onModelChange={setSelectedModel} />
47
- <FileTree projectPath={projectPath} onFileSelect={setActiveFile} />
48
- </div>
49
- )}
50
- {!sidebarVisible && (
51
- <div style={{ width: 40, background: 'var(--bg-secondary)', borderRight: '1px solid var(--border)', display: 'flex', alignItems: 'flex-start', paddingTop: 10, justifyContent: 'center' }}>
52
- <span onClick={() => setSidebarVisible(true)} style={{ cursor: 'pointer', color: 'var(--text-secondary)' }}>☰</span>
53
- </div>
54
- )}
55
- <PanelGroup direction="vertical" style={{ flex: 1 }}>
56
- <Panel defaultSize={65} minSize={20}>
57
- <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
58
- <div style={{ display: 'flex', background: 'var(--bg-tertiary)', borderBottom: '1px solid var(--border)', padding: '0 8px', overflowX: 'auto' }}>
59
- {activeFile ? (
60
- <div style={{ padding: '8px 12px', borderBottom: '2px solid var(--accent)', color: 'var(--accent)', fontSize: 13 }}>📄 {activeFile}</div>
61
- ) : (
62
- <div style={{ padding: '8px 12px', color: 'var(--text-secondary)', fontSize: 13 }}>No file selected</div>
63
- )}
64
- </div>
65
- <CodeEditor filePath={activeFile} projectPath={projectPath} />
66
- </div>
67
- </Panel>
68
- <PanelResizeHandle style={{ height: 4, background: 'var(--border)' }} />
69
- <Panel defaultSize={35} minSize={15}>
70
- <div style={{ height: '100%', display: 'flex', flexDirection: 'column', background: 'var(--bg-primary)' }}>
71
- <div style={{ display: 'flex', background: 'var(--bg-tertiary)', borderBottom: '1px solid var(--border)', padding: '0 8px' }}>
72
- <button onClick={() => setActiveBottom('chat')} style={{ padding: '8px 16px', background: activeBottom === 'chat' ? 'var(--bg-primary)' : 'transparent', borderBottom: activeBottom === 'chat' ? '2px solid var(--accent)' : 'none', color: activeBottom === 'chat' ? 'var(--accent)' : 'var(--text-secondary)' }}><FaComments /> Chat (Ctrl+1)</button>
73
- <button onClick={() => setActiveBottom('agent')} style={{ padding: '8px 16px', background: activeBottom === 'agent' ? 'var(--bg-primary)' : 'transparent', borderBottom: activeBottom === 'agent' ? '2px solid var(--accent)' : 'none', color: activeBottom === 'agent' ? 'var(--accent)' : 'var(--text-secondary)' }}><FaRobot /> Agent (Ctrl+2)</button>
74
- <button onClick={() => setActiveBottom('terminal')} style={{ padding: '8px 16px', background: activeBottom === 'terminal' ? 'var(--bg-primary)' : 'transparent', borderBottom: activeBottom === 'terminal' ? '2px solid var(--accent)' : 'none', color: activeBottom === 'terminal' ? 'var(--accent)' : 'var(--text-secondary)' }}><FaTerminal /> Terminal (Ctrl+3)</button>
75
- <button onClick={() => setActiveBottom('git')} style={{ padding: '8px 16px', background: activeBottom === 'git' ? 'var(--bg-primary)' : 'transparent', borderBottom: activeBottom === 'git' ? '2px solid var(--accent)' : 'none', color: activeBottom === 'git' ? 'var(--accent)' : 'var(--text-secondary)' }}><FaCodeBranch /> Git (Ctrl+4)</button>
76
- <button onClick={() => setActiveBottom('tasks')} style={{ padding: '8px 16px', background: activeBottom === 'tasks' ? 'var(--bg-primary)' : 'transparent', borderBottom: activeBottom === 'tasks' ? '2px solid var(--accent)' : 'none', color: activeBottom === 'tasks' ? 'var(--accent)' : 'var(--text-secondary)' }}><FaTasks /> Tasks (Ctrl+5)</button>
77
- </div>
78
- <div style={{ flex: 1, overflow: 'hidden' }}>
79
- {activeBottom === 'chat' && <ChatPanel projectPath={projectPath} model={selectedModel} />}
80
- {activeBottom === 'agent' && <AgentPanel projectPath={projectPath} />}
81
- {activeBottom === 'terminal' && <TerminalTabs />}
82
- {activeBottom === 'git' && <GitPanel projectPath={projectPath} />}
83
- {activeBottom === 'tasks' && <BackgroundTasks />}
84
- </div>
85
- </div>
86
- </Panel>
87
- </PanelGroup>
88
- </div>
89
- <StatusBar model={selectedModel} />
90
- </div>
91
- );
92
- };
93
-
94
- export default App;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/components/AgentPanel.tsx DELETED
@@ -1,141 +0,0 @@
1
- import React, { useState } from 'react';
2
- import { FaRobot, FaSpinner, FaCheck, FaTimes, FaChevronLeft, FaChevronRight } from 'react-icons/fa';
3
- import { DiffEditor } from '@monaco-editor/react';
4
-
5
- interface Change {
6
- filepath: string;
7
- action: string;
8
- search?: string;
9
- replace?: string;
10
- content?: string;
11
- }
12
-
13
- const AgentPanel: React.FC<{ projectPath: string }> = ({ projectPath }) => {
14
- const [prompt, setPrompt] = useState('');
15
- const [changes, setChanges] = useState<Change[]>([]);
16
- const [approved, setApproved] = useState<Set<number>>(new Set());
17
- const [currentDiff, setCurrentDiff] = useState(0);
18
- const [planning, setPlanning] = useState(false);
19
- const [applying, setApplying] = useState(false);
20
- const [output, setOutput] = useState('');
21
-
22
- const runPlan = async () => {
23
- setPlanning(true);
24
- setOutput('Planning...');
25
- setChanges([]);
26
- setApproved(new Set());
27
- try {
28
- const resp = await fetch('/api/agent/plan', {
29
- method: 'POST',
30
- headers: { 'Content-Type': 'application/json' },
31
- body: JSON.stringify({ prompt, project_path: projectPath, auto_approve: false }),
32
- });
33
- const data = await resp.json();
34
- setChanges(data.changes || []);
35
- setOutput(`Agent proposed ${data.changes?.length || 0} changes.`);
36
- } catch (e) {
37
- setOutput('Error: ' + e);
38
- }
39
- setPlanning(false);
40
- };
41
-
42
- const applyApproved = async () => {
43
- setApplying(true);
44
- const indices = Array.from(approved);
45
- try {
46
- const resp = await fetch('/api/agent/apply', {
47
- method: 'POST',
48
- headers: { 'Content-Type': 'application/json' },
49
- body: JSON.stringify({ task_id: 'default', approved_indices: indices }),
50
- });
51
- const data = await resp.json();
52
- setOutput(prev => prev + '\n' + (data.logs || []).join('\n'));
53
- } catch (e) {
54
- setOutput('Error applying: ' + e);
55
- }
56
- setApplying(false);
57
- };
58
-
59
- const toggleApproval = (idx: number) => {
60
- const newSet = new Set(approved);
61
- if (newSet.has(idx)) newSet.delete(idx); else newSet.add(idx);
62
- setApproved(newSet);
63
- };
64
-
65
- const diffOriginal = (change: Change) => {
66
- if (change.action === 'create') return '';
67
- return change.search || '';
68
- };
69
-
70
- const diffModified = (change: Change) => {
71
- if (change.action === 'create') return change.content || '';
72
- return change.replace || '';
73
- };
74
-
75
- return (
76
- <div style={{ height: '100%', display: 'flex', flexDirection: 'column', background: 'var(--bg-primary)' }}>
77
- <div style={{ padding: 12, borderBottom: '1px solid var(--border)' }}>
78
- <div style={{ fontWeight: 600, marginBottom: 8 }}><FaRobot /> AI Agent</div>
79
- <textarea
80
- style={{ width: '100%', height: 60, background: 'var(--bg-tertiary)', color: 'var(--text-primary)', border: '1px solid var(--border)', borderRadius: 6, padding: 8, resize: 'vertical' }}
81
- value={prompt}
82
- onChange={e => setPrompt(e.target.value)}
83
- placeholder="E.g., Convert all React components to TypeScript..."
84
- />
85
- <div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
86
- <button onClick={runPlan} disabled={planning} style={{ background: 'var(--accent)', color: 'white', padding: '6px 16px', borderRadius: 6, fontWeight: 600 }}>
87
- {planning ? <><FaSpinner className="spin" /> Planning</> : 'Plan Changes'}
88
- </button>
89
- {changes.length > 0 && (
90
- <button onClick={applyApproved} disabled={applying || approved.size === 0} style={{ background: 'var(--success)', color: 'white', padding: '6px 16px', borderRadius: 6, fontWeight: 600 }}>
91
- {applying ? 'Applying...' : `Apply ${approved.size} Approved`}
92
- </button>
93
- )}
94
- </div>
95
- </div>
96
-
97
- {changes.length > 0 && (
98
- <div style={{ borderBottom: '1px solid var(--border)', flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
99
- <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '4px 12px', background: 'var(--bg-secondary)' }}>
100
- <div style={{ display: 'flex', gap: 12 }}>
101
- <button onClick={() => setCurrentDiff(Math.max(0, currentDiff - 1))}><FaChevronLeft /></button>
102
- <span style={{ fontSize: 13 }}>File {currentDiff + 1} of {changes.length}: <strong>{changes[currentDiff]?.filepath}</strong></span>
103
- <button onClick={() => setCurrentDiff(Math.min(changes.length - 1, currentDiff + 1))}><FaChevronRight /></button>
104
- </div>
105
- <button
106
- onClick={() => toggleApproval(currentDiff)}
107
- style={{
108
- background: approved.has(currentDiff) ? 'var(--success)' : 'var(--danger)',
109
- color: 'white',
110
- padding: '4px 12px',
111
- borderRadius: 4,
112
- display: 'flex',
113
- alignItems: 'center',
114
- gap: 4,
115
- fontSize: 12,
116
- }}
117
- >
118
- {approved.has(currentDiff) ? <><FaCheck /> Approved</> : <><FaTimes /> Rejected</>}
119
- </button>
120
- </div>
121
- <div style={{ flex: 1, minHeight: 0 }}>
122
- <DiffEditor
123
- height="100%"
124
- original={diffOriginal(changes[currentDiff])}
125
- modified={diffModified(changes[currentDiff])}
126
- language="typescript"
127
- theme="vs-dark"
128
- options={{ readOnly: true, renderSideBySide: true }}
129
- />
130
- </div>
131
- </div>
132
- )}
133
-
134
- <div style={{ padding: 12, background: 'var(--bg-secondary)', maxHeight: 150, overflowY: 'auto', fontFamily: 'monospace', fontSize: 12, whiteSpace: 'pre-wrap' }}>
135
- {output}
136
- </div>
137
- </div>
138
- );
139
- };
140
-
141
- export default AgentPanel;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/components/BackgroundTasks.tsx DELETED
@@ -1,33 +0,0 @@
1
- import React, { useState, useEffect } from 'react';
2
- import axios from 'axios';
3
-
4
- const BackgroundTasks: React.FC = () => {
5
- const [tasks, setTasks] = useState<string[]>([]);
6
-
7
- const pollTasks = async () => {
8
- try {
9
- const res = await axios.get('/api/agent/tasks');
10
- setTasks(res.data.tasks || []);
11
- } catch (e) {}
12
- };
13
-
14
- useEffect(() => {
15
- pollTasks();
16
- const interval = setInterval(pollTasks, 3000);
17
- return () => clearInterval(interval);
18
- }, []);
19
-
20
- return (
21
- <div style={{ padding: 12 }}>
22
- <div style={{ fontWeight: 600, marginBottom: 8 }}>Background Agents</div>
23
- {tasks.length === 0 && <div style={{ color: 'var(--text-secondary)', fontSize: 12 }}>No running tasks</div>}
24
- {tasks.map(task => (
25
- <div key={task} style={{ background: 'var(--bg-tertiary)', padding: '4px 8px', borderRadius: 4, marginBottom: 4, fontSize: 12 }}>
26
- {task} – running
27
- </div>
28
- ))}
29
- </div>
30
- );
31
- };
32
-
33
- export default BackgroundTasks;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/components/ChatPanel.tsx DELETED
@@ -1,251 +0,0 @@
1
- import React, { useState, useRef, useEffect } from 'react';
2
- import ReactMarkdown from 'react-markdown';
3
- import { FaUser, FaRobot, FaSpinner } from 'react-icons/fa';
4
- import axios from 'axios';
5
-
6
- interface Message {
7
- role: 'user' | 'assistant';
8
- content: string;
9
- }
10
-
11
- const ChatPanel: React.FC<{ projectPath: string; model: string }> = ({ projectPath, model }) => {
12
- const [messages, setMessages] = useState<Message[]>([]);
13
- const [input, setInput] = useState('');
14
- const [loading, setLoading] = useState(false);
15
- const [mentionQuery, setMentionQuery] = useState('');
16
- const [mentionItems, setMentionItems] = useState<any[]>([]);
17
- const [showMentions, setShowMentions] = useState(false);
18
- const [contextFiles, setContextFiles] = useState<string[]>([]);
19
- const chatEnd = useRef<HTMLDivElement>(null);
20
- const inputRef = useRef<HTMLInputElement>(null);
21
-
22
- const send = async () => {
23
- if (!input.trim() || loading) return;
24
- const userMsg = input;
25
- setInput('');
26
- setMessages(prev => [...prev, { role: 'user', content: userMsg }]);
27
- setLoading(true);
28
-
29
- const assistantMsgRef = { current: '' };
30
- setMessages(prev => [...prev, { role: 'assistant', content: '' }]);
31
-
32
- try {
33
- const response = await fetch('/api/chat', {
34
- method: 'POST',
35
- headers: { 'Content-Type': 'application/json' },
36
- body: JSON.stringify({
37
- messages: [...messages, { role: 'user', content: userMsg }],
38
- project_path: projectPath,
39
- model: model,
40
- context_files: contextFiles,
41
- }),
42
- });
43
-
44
- if (!response.ok) throw new Error(`Server responded with ${response.status}`);
45
-
46
- const reader = response.body?.getReader();
47
- if (!reader) throw new Error('No response body');
48
-
49
- const decoder = new TextDecoder();
50
- let buffer = '';
51
-
52
- while (true) {
53
- const { done, value } = await reader.read();
54
- if (done) break;
55
- buffer += decoder.decode(value, { stream: true });
56
- const lines = buffer.split('\n');
57
- buffer = lines.pop() || '';
58
- for (const line of lines) {
59
- if (line.startsWith('data: ')) {
60
- const data = line.slice(6);
61
- if (data === '[DONE]') break;
62
- try {
63
- const { token } = JSON.parse(data);
64
- if (token.startsWith('ERROR:')) {
65
- assistantMsgRef.current = `❌ ${token.substring(7)}`;
66
- setMessages(prev => {
67
- const copy = [...prev];
68
- copy[copy.length - 1] = { role: 'assistant', content: assistantMsgRef.current };
69
- return copy;
70
- });
71
- return;
72
- }
73
- assistantMsgRef.current += token;
74
- setMessages(prev => {
75
- const copy = [...prev];
76
- copy[copy.length - 1] = { role: 'assistant', content: assistantMsgRef.current };
77
- return copy;
78
- });
79
- } catch (e) {
80
- assistantMsgRef.current = `Parse error: ${data}`;
81
- setMessages(prev => {
82
- const copy = [...prev];
83
- copy[copy.length - 1] = { role: 'assistant', content: assistantMsgRef.current };
84
- return copy;
85
- });
86
- return;
87
- }
88
- }
89
- }
90
- }
91
- } catch (err: any) {
92
- setMessages(prev => {
93
- const copy = [...prev];
94
- copy[copy.length - 1] = { role: 'assistant', content: `❌ ${err.message}` };
95
- return copy;
96
- });
97
- } finally {
98
- setLoading(false);
99
- }
100
- };
101
-
102
- useEffect(() => { chatEnd.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]);
103
-
104
- const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
105
- const value = e.target.value;
106
- setInput(value);
107
-
108
- const atIndex = value.lastIndexOf('@');
109
- if (atIndex !== -1 && (atIndex === 0 || value[atIndex - 1] === ' ')) {
110
- const query = value.slice(atIndex + 1).toLowerCase();
111
- setMentionQuery(query);
112
-
113
- if ('git'.startsWith(query)) {
114
- setMentionItems([{ name: 'git (current diff)', type: 'special', path: '__git__' }]);
115
- setShowMentions(true);
116
- return;
117
- }
118
-
119
- axios.get('/api/files', { params: { project_path: projectPath } })
120
- .then(res => {
121
- const flatten = (nodes: any[]): any[] => {
122
- let result: any[] = [];
123
- for (const node of nodes) {
124
- if (node.name.toLowerCase().includes(query))
125
- result.push(node);
126
- if (node.children) result = result.concat(flatten(node.children));
127
- }
128
- return result;
129
- };
130
- const matches = flatten(res.data).slice(0, 5);
131
- setMentionItems(matches);
132
- setShowMentions(matches.length > 0);
133
- })
134
- .catch(() => setShowMentions(false));
135
- } else {
136
- setShowMentions(false);
137
- }
138
- };
139
-
140
- const insertMention = (item: any) => {
141
- const atIndex = input.lastIndexOf('@');
142
- const newInput = input.slice(0, atIndex) + item.name + ' ';
143
- setInput(newInput);
144
- setShowMentions(false);
145
- if (item.path === '__git__') {
146
- if (!contextFiles.includes('__git__')) {
147
- setContextFiles(prev => [...prev, '__git__']);
148
- }
149
- } else if (!contextFiles.includes(item.path)) {
150
- setContextFiles(prev => [...prev, item.path]);
151
- }
152
- inputRef.current?.focus();
153
- };
154
-
155
- return (
156
- <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
157
- <div style={{ flex: 1, overflowY: 'auto', padding: 16 }}>
158
- {messages.map((m, i) => (
159
- <div key={i} style={{ marginBottom: 16, display: 'flex', justifyContent: m.role === 'user' ? 'flex-end' : 'flex-start' }}>
160
- <div style={{
161
- maxWidth: '80%',
162
- background: m.role === 'user' ? 'var(--accent)' : 'var(--bg-tertiary)',
163
- color: m.role === 'user' ? 'white' : 'var(--text-primary)',
164
- borderRadius: 12,
165
- borderBottomRightRadius: m.role === 'user' ? 4 : 12,
166
- borderBottomLeftRadius: m.role === 'user' ? 12 : 4,
167
- padding: 12,
168
- boxShadow: '0 2px 8px rgba(0,0,0,0.15)'
169
- }}>
170
- <div style={{ fontSize: 11, marginBottom: 4, display: 'flex', alignItems: 'center', gap: 4, color: m.role === 'user' ? 'rgba(255,255,255,0.8)' : 'var(--text-secondary)' }}>
171
- {m.role === 'user' ? <FaUser size={10} /> : <FaRobot size={10} />}
172
- {m.role === 'user' ? 'You' : 'Codeki'}
173
- </div>
174
- <ReactMarkdown>{m.content}</ReactMarkdown>
175
- </div>
176
- </div>
177
- ))}
178
- {loading && (
179
- <div style={{ display: 'flex', alignItems: 'center', gap: 8, color: 'var(--text-secondary)', padding: 8 }}>
180
- <FaSpinner className="spin" /> Codeki is thinking...
181
- </div>
182
- )}
183
- <div ref={chatEnd} />
184
- </div>
185
- <div style={{ borderTop: '1px solid var(--border)', padding: 12, background: 'var(--bg-secondary)', position: 'relative' }}>
186
- {showMentions && (
187
- <div style={{
188
- position: 'absolute',
189
- bottom: '100%',
190
- left: 0,
191
- right: 0,
192
- background: 'var(--bg-tertiary)',
193
- border: '1px solid var(--border)',
194
- borderRadius: 4,
195
- maxHeight: 150,
196
- overflowY: 'auto',
197
- marginBottom: 4,
198
- zIndex: 10,
199
- }}>
200
- {mentionItems.map(item => (
201
- <div
202
- key={item.path}
203
- onClick={() => insertMention(item)}
204
- style={{ padding: '4px 8px', cursor: 'pointer', fontSize: 12, display: 'flex', alignItems: 'center', gap: 6 }}
205
- >
206
- {item.type === 'folder' ? '📁' : item.type === 'special' ? '🔗' : '📄'} {item.name}
207
- </div>
208
- ))}
209
- </div>
210
- )}
211
- <div style={{ display: 'flex', gap: 8 }}>
212
- <input
213
- ref={inputRef}
214
- style={{ flex: 1 }}
215
- value={input}
216
- onChange={handleInputChange}
217
- onKeyDown={e => e.key === 'Enter' && send()}
218
- placeholder="Ask anything... Type @ to reference a file or @git"
219
- disabled={loading}
220
- />
221
- <button
222
- onClick={send}
223
- disabled={loading}
224
- style={{
225
- background: loading ? 'var(--border)' : 'var(--accent)',
226
- color: 'white',
227
- padding: '0 20px',
228
- borderRadius: 6,
229
- fontWeight: 600,
230
- transition: 'background 0.2s'
231
- }}
232
- >
233
- {loading ? '...' : 'Send'}
234
- </button>
235
- </div>
236
- {contextFiles.length > 0 && (
237
- <div style={{ marginTop: 6, display: 'flex', gap: 4, flexWrap: 'wrap' }}>
238
- {contextFiles.map(f => (
239
- <span key={f} style={{ background: 'var(--accent)', color: 'white', padding: '2px 8px', borderRadius: 10, fontSize: 11 }}>
240
- 📎 {f === '__git__' ? 'Git Diff' : f.split('/').pop()}
241
- <span style={{ marginLeft: 4, cursor: 'pointer' }} onClick={() => setContextFiles(prev => prev.filter(x => x !== f))}>×</span>
242
- </span>
243
- ))}
244
- </div>
245
- )}
246
- </div>
247
- </div>
248
- );
249
- };
250
-
251
- export default ChatPanel;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/components/CodeEditor.tsx DELETED
@@ -1,86 +0,0 @@
1
- import React, { useRef } from 'react';
2
- import Editor, { OnMount } from '@monaco-editor/react';
3
- import * as monaco from 'monaco-editor';
4
-
5
- const CodeEditor: React.FC<{ filePath: string | null; projectPath: string }> = ({ filePath, projectPath }) => {
6
- const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
7
-
8
- const handleEditorDidMount: OnMount = (editor, monaco) => {
9
- editorRef.current = editor;
10
-
11
- monaco.languages.registerInlineCompletionsProvider('*', {
12
- provideInlineCompletions: async (model, position, context, token) => {
13
- const prefix = model.getValueInRange({
14
- startLineNumber: 1,
15
- startColumn: 1,
16
- endLineNumber: position.lineNumber,
17
- endColumn: position.column,
18
- });
19
- const suffix = model.getValueInRange({
20
- startLineNumber: position.lineNumber,
21
- startColumn: position.column,
22
- endLineNumber: model.getLineCount(),
23
- endColumn: model.getLineMaxColumn(model.getLineCount()),
24
- });
25
-
26
- try {
27
- const resp = await fetch('/api/autocomplete', {
28
- method: 'POST',
29
- headers: { 'Content-Type': 'application/json' },
30
- body: JSON.stringify({
31
- prefix,
32
- suffix,
33
- filepath: filePath || '',
34
- language: model.getLanguageId(),
35
- max_tokens: 64,
36
- model: 'groq-llama-3.1-8b-instant',
37
- }),
38
- });
39
- if (!resp.ok) return { items: [] };
40
- const data = await resp.json();
41
- const completions = data.completions || [];
42
- return {
43
- items: completions.map((c: any) => ({
44
- insertText: c.text,
45
- range: {
46
- startLineNumber: position.lineNumber,
47
- startColumn: position.column,
48
- endLineNumber: position.lineNumber,
49
- endColumn: position.column,
50
- },
51
- })),
52
- };
53
- } catch (e) {
54
- return { items: [] };
55
- }
56
- },
57
- freeInlineCompletions: () => {},
58
- });
59
- };
60
-
61
- if (!filePath) return (
62
- <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--text-secondary)', fontSize: 14 }}>
63
- ← Select a file from the explorer to start editing
64
- </div>
65
- );
66
-
67
- return (
68
- <Editor
69
- height="100%"
70
- defaultLanguage="typescript"
71
- theme="vs-dark"
72
- path={filePath}
73
- onMount={handleEditorDidMount}
74
- loading={<div style={{ color: 'var(--text-secondary)', padding: 20 }}>Loading editor...</div>}
75
- options={{
76
- fontSize: 13,
77
- minimap: { enabled: true },
78
- smoothScrolling: true,
79
- cursorBlinking: 'smooth',
80
- inlineSuggest: { enabled: true },
81
- }}
82
- />
83
- );
84
- };
85
-
86
- export default CodeEditor;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/components/CodebaseExplorer.tsx DELETED
@@ -1,3 +0,0 @@
1
- import React from 'react';
2
- const CodebaseExplorer: React.FC<{ projectPath: string }> = () => <div style={{ color: 'white' }}>Files</div>;
3
- export default CodebaseExplorer;
 
 
 
 
frontend/src/components/FileTree.tsx DELETED
@@ -1,83 +0,0 @@
1
- import React, { useState, useEffect } from 'react';
2
- import { FaFolder, FaFolderOpen, FaFile, FaChevronRight, FaChevronDown, FaSpinner } from 'react-icons/fa';
3
- import axios from 'axios';
4
-
5
- interface TreeNode {
6
- name: string;
7
- path: string;
8
- type: 'file' | 'folder';
9
- children?: TreeNode[];
10
- }
11
-
12
- const FileTree: React.FC<{ projectPath: string; onFileSelect: (path: string) => void }> = ({ projectPath, onFileSelect }) => {
13
- const [tree, setTree] = useState<TreeNode[]>([]);
14
- const [expanded, setExpanded] = useState<Set<string>>(new Set());
15
- const [selected, setSelected] = useState<string | null>(null);
16
- const [loading, setLoading] = useState(true);
17
-
18
- useEffect(() => {
19
- axios.get('/api/files', { params: { project_path: projectPath } })
20
- .then(res => setTree(res.data))
21
- .catch(() => setTree([]))
22
- .finally(() => setLoading(false));
23
- }, [projectPath]);
24
-
25
- const toggle = (path: string) => {
26
- setExpanded(prev => {
27
- const next = new Set(prev);
28
- if (next.has(path)) next.delete(path); else next.add(path);
29
- return next;
30
- });
31
- };
32
-
33
- const handleClick = (node: TreeNode) => {
34
- if (node.type === 'folder') {
35
- toggle(node.path);
36
- } else {
37
- setSelected(node.path);
38
- onFileSelect(node.path);
39
- }
40
- };
41
-
42
- const renderNode = (node: TreeNode, depth: number = 0) => {
43
- const isOpen = expanded.has(node.path);
44
- return (
45
- <div key={node.path}>
46
- <div
47
- onClick={() => handleClick(node)}
48
- style={{
49
- paddingLeft: depth * 16 + 8,
50
- padding: '4px 8px',
51
- cursor: 'pointer',
52
- display: 'flex',
53
- alignItems: 'center',
54
- gap: 6,
55
- background: selected === node.path ? 'var(--accent)' : 'transparent',
56
- color: selected === node.path ? 'white' : 'var(--text-primary)',
57
- fontSize: 13,
58
- }}
59
- >
60
- {node.type === 'folder' ? (
61
- <>
62
- {isOpen ? <FaChevronDown size={10} /> : <FaChevronRight size={10} />}
63
- {isOpen ? <FaFolderOpen size={14} /> : <FaFolder size={14} />}
64
- </>
65
- ) : (
66
- <>
67
- <span style={{ width: 10 }} />
68
- <FaFile size={14} />
69
- </>
70
- )}
71
- <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{node.name}</span>
72
- </div>
73
- {node.type === 'folder' && isOpen && node.children?.map(child => renderNode(child, depth + 1))}
74
- </div>
75
- );
76
- };
77
-
78
- if (loading) return <div style={{ padding: 12, color: 'var(--text-secondary)' }}><FaSpinner className="spin" /> Loading files...</div>;
79
-
80
- return <div style={{ overflowY: 'auto', flex: 1 }}>{tree.map(node => renderNode(node))}</div>;
81
- };
82
-
83
- export default FileTree;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/components/GitPanel.tsx DELETED
@@ -1,148 +0,0 @@
1
- import React, { useState, useEffect } from 'react';
2
- import axios from 'axios';
3
- import { FaCodeBranch, FaHistory, FaPen, FaFileAlt } from 'react-icons/fa';
4
- import ReactMarkdown from 'react-markdown';
5
-
6
- const GitPanel: React.FC<{ projectPath: string }> = ({ projectPath }) => {
7
- const [branches, setBranches] = useState<string[]>([]);
8
- const [logs, setLogs] = useState<any[]>([]);
9
- const [commitMsg, setCommitMsg] = useState('');
10
- const [generating, setGenerating] = useState(false);
11
- const [prSummary, setPrSummary] = useState('');
12
-
13
- const fetchData = async () => {
14
- try {
15
- const [branchesRes, logsRes] = await Promise.all([
16
- axios.post('/api/git', { action: 'branch', project_path: projectPath }),
17
- axios.post('/api/git', { action: 'log', project_path: projectPath })
18
- ]);
19
- setBranches(branchesRes.data.branches || []);
20
- setLogs(logsRes.data.log || []);
21
- } catch (e) {}
22
- };
23
-
24
- useEffect(() => { fetchData(); }, []);
25
-
26
- const generateCommitMessage = async () => {
27
- setGenerating(true);
28
- try {
29
- const diffRes = await axios.post('/api/git', { action: 'diff', project_path: projectPath });
30
- const diff = diffRes.data.diff || '';
31
- const resp = await fetch('/api/chat', {
32
- method: 'POST',
33
- headers: { 'Content-Type': 'application/json' },
34
- body: JSON.stringify({
35
- model: 'groq-llama-3.1-8b-instant',
36
- messages: [{ role: 'user', content: `Generate a concise git commit message for the following diff:\n${diff}` }],
37
- project_path: projectPath,
38
- }),
39
- });
40
- const reader = resp.body?.getReader();
41
- let msg = '';
42
- while (reader) {
43
- const { done, value } = await reader.read();
44
- if (done) break;
45
- const text = new TextDecoder().decode(value);
46
- const lines = text.split('\n').filter(l => l.startsWith('data: ')).map(l => l.slice(6));
47
- for (const line of lines) {
48
- if (line === '[DONE]') break;
49
- try {
50
- const { token } = JSON.parse(line);
51
- msg += token;
52
- } catch (e) {}
53
- }
54
- }
55
- setCommitMsg(msg.replace(/ERROR:.*/, '').trim());
56
- } catch (e) {}
57
- setGenerating(false);
58
- };
59
-
60
- const generatePRSummary = async () => {
61
- try {
62
- const diffRes = await axios.post('/api/git', { action: 'diff', project_path: projectPath });
63
- const diff = diffRes.data.diff || '';
64
- const resp = await fetch('/api/chat', {
65
- method: 'POST',
66
- headers: { 'Content-Type': 'application/json' },
67
- body: JSON.stringify({
68
- model: 'groq-llama-3.3-70b-versatile',
69
- messages: [{ role: 'user', content: `Review the following diff and provide a PR summary with suggestions:\n${diff}` }],
70
- project_path: projectPath,
71
- }),
72
- });
73
- const reader = resp.body?.getReader();
74
- let summary = '';
75
- while (reader) {
76
- const { done, value } = await reader.read();
77
- if (done) break;
78
- const text = new TextDecoder().decode(value);
79
- const lines = text.split('\n').filter(l => l.startsWith('data: ')).map(l => l.slice(6));
80
- for (const line of lines) {
81
- if (line === '[DONE]') break;
82
- try {
83
- const { token } = JSON.parse(line);
84
- summary += token;
85
- } catch (e) {}
86
- }
87
- }
88
- setPrSummary(summary);
89
- } catch (e) {}
90
- };
91
-
92
- const commit = async () => {
93
- await axios.post('/api/git', { action: 'commit', project_path: projectPath, args: { message: commitMsg } });
94
- fetchData();
95
- setCommitMsg('');
96
- };
97
-
98
- return (
99
- <div style={{ height: '100%', display: 'flex', flexDirection: 'column', background: 'var(--bg-primary)', padding: 12, overflowY: 'auto' }}>
100
- <div style={{ fontWeight: 600, marginBottom: 8 }}><FaCodeBranch /> Git</div>
101
-
102
- <div style={{ marginBottom: 12 }}>
103
- <div style={{ fontSize: 12, color: 'var(--text-secondary)', marginBottom: 4 }}>Branches</div>
104
- {branches.map(b => (
105
- <div key={b} style={{ padding: '2px 8px', background: 'var(--bg-tertiary)', borderRadius: 4, marginBottom: 2, fontSize: 13 }}>{b}</div>
106
- ))}
107
- </div>
108
-
109
- <div style={{ marginBottom: 12 }}>
110
- <div style={{ fontSize: 12, color: 'var(--text-secondary)', marginBottom: 4 }}>Recent Commits</div>
111
- {logs.map((log, i) => (
112
- <div key={i} style={{ borderBottom: '1px solid var(--border)', padding: '4px 0', fontSize: 12 }}>
113
- <span style={{ color: 'var(--accent)' }}>{log.hash?.substring(0,7)} </span>
114
- {log.message}
115
- </div>
116
- ))}
117
- </div>
118
-
119
- <div style={{ marginBottom: 12 }}>
120
- <textarea
121
- style={{ width: '100%', height: 40, background: 'var(--bg-tertiary)', color: 'var(--text-primary)', border: '1px solid var(--border)', borderRadius: 4, padding: 4, fontSize: 12 }}
122
- placeholder="Commit message..."
123
- value={commitMsg}
124
- onChange={e => setCommitMsg(e.target.value)}
125
- />
126
- <div style={{ display: 'flex', gap: 8, marginTop: 4 }}>
127
- <button onClick={generateCommitMessage} disabled={generating} style={{ background: 'var(--accent)', color: 'white', padding: '4px 8px', borderRadius: 4, fontSize: 12, display: 'flex', alignItems: 'center', gap: 4 }}>
128
- <FaPen /> {generating ? '...' : 'AI Message'}
129
- </button>
130
- <button onClick={commit} style={{ background: 'var(--success)', color: 'white', padding: '4px 8px', borderRadius: 4, fontSize: 12 }}>Commit</button>
131
- </div>
132
- </div>
133
-
134
- <div style={{ marginBottom: 12 }}>
135
- <button onClick={generatePRSummary} style={{ background: 'var(--accent)', color: 'white', padding: '4px 12px', borderRadius: 4, fontSize: 12, display: 'flex', alignItems: 'center', gap: 4 }}>
136
- <FaFileAlt /> Generate PR Summary
137
- </button>
138
- {prSummary && (
139
- <div style={{ marginTop: 8, padding: 8, background: 'var(--bg-secondary)', borderRadius: 4 }}>
140
- <ReactMarkdown>{prSummary}</ReactMarkdown>
141
- </div>
142
- )}
143
- </div>
144
- </div>
145
- );
146
- };
147
-
148
- export default GitPanel;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/components/ModelSelector.tsx DELETED
@@ -1,32 +0,0 @@
1
- import React, { useState, useEffect } from 'react';
2
- import axios from 'axios';
3
- import { FaRobot } from 'react-icons/fa';
4
-
5
- const ModelSelector: React.FC<{ selectedModel: string; onModelChange: (model: string) => void }> = ({ selectedModel, onModelChange }) => {
6
- const [models, setModels] = useState<any[]>([]);
7
-
8
- useEffect(() => {
9
- axios.get('/api/models')
10
- .then(res => setModels(res.data))
11
- .catch(() => setModels([]));
12
- }, []);
13
-
14
- return (
15
- <div style={{ padding: '8px 12px', borderBottom: '1px solid var(--border)' }}>
16
- <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 6, color: 'var(--text-secondary)', fontSize: 12 }}>
17
- <FaRobot /> ACTIVE MODEL
18
- </div>
19
- <select
20
- style={{ width: '100%', background: 'var(--bg-tertiary)', color: 'var(--text-primary)', border: '1px solid var(--border)', padding: '6px 8px', borderRadius: 6, outline: 'none', fontSize: 13 }}
21
- value={selectedModel}
22
- onChange={e => onModelChange(e.target.value)}
23
- >
24
- {models.map((m: any) => (
25
- <option key={m.id} value={m.id}>{m.name}</option>
26
- ))}
27
- </select>
28
- </div>
29
- );
30
- };
31
-
32
- export default ModelSelector;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/components/StatusBar.tsx DELETED
@@ -1,16 +0,0 @@
1
- import React from 'react';
2
- import { FaMicrochip, FaPlug } from 'react-icons/fa';
3
-
4
- const StatusBar: React.FC<{ model: string }> = ({ model }) => {
5
- return (
6
- <div style={{ height: 24, background: 'var(--bg-tertiary)', borderTop: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 12px', fontSize: 12, color: 'var(--text-secondary)' }}>
7
- <div style={{ display: 'flex', gap: 15 }}>
8
- <span><FaPlug style={{ color: 'var(--success)' }} /> Connected</span>
9
- <span><FaMicrochip /> {model}</span>
10
- </div>
11
- <span>Ln 1, Col 1</span>
12
- </div>
13
- );
14
- };
15
-
16
- export default StatusBar;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/components/Terminal.tsx DELETED
@@ -1,242 +0,0 @@
1
-
2
- # Fixed Terminal.tsx - proper TypeScript with correct escaping
3
- terminal_tsx = r'''import React, { useState, useEffect, useRef, useCallback } from 'react';
4
- import { Terminal } from 'xterm';
5
- import { FitAddon } from 'xterm-addon-fit';
6
- import 'xterm/css/xterm.css';
7
-
8
- interface Tab {
9
- id: number;
10
- terminal: Terminal;
11
- socket: WebSocket;
12
- fitAddon: FitAddon;
13
- }
14
-
15
- const TerminalTabs: React.FC = () => {
16
- const [tabs, setTabs] = useState<Tab[]>([]);
17
- const [activeTab, setActiveTab] = useState<number>(0);
18
- const termRefs = useRef<Map<number, HTMLDivElement>>(new Map());
19
- const nextId = useRef(1);
20
-
21
- const createTerminal = useCallback(() => {
22
- const id = nextId.current++;
23
- const term = new Terminal({
24
- cursorBlink: true,
25
- theme: {
26
- background: '#0d1117',
27
- foreground: '#c9d1d9',
28
- cursor: '#58a6ff',
29
- black: '#484f58',
30
- red: '#f85149',
31
- green: '#3fb950',
32
- yellow: '#d29922',
33
- blue: '#58a6ff',
34
- magenta: '#bc8cff',
35
- cyan: '#39c5cf',
36
- white: '#c9d1d9',
37
- },
38
- fontSize: 13,
39
- fontFamily: 'monospace',
40
- rows: 24,
41
- cols: 80,
42
- });
43
-
44
- const fitAddon = new FitAddon();
45
- term.loadAddon(fitAddon);
46
-
47
- const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
48
- const socket = new WebSocket(`${protocol}//${window.location.host}/ws/terminal`);
49
-
50
- socket.onopen = () => {
51
- term.writeln('\r\n\x1b[32mConnected to terminal\x1b[0m\r\n');
52
- };
53
-
54
- socket.onmessage = (event) => {
55
- term.write(event.data);
56
- };
57
-
58
- socket.onerror = () => {
59
- term.writeln('\r\n\x1b[31mConnection error\x1b[0m\r\n');
60
- };
61
-
62
- socket.onclose = () => {
63
- term.writeln('\r\n\x1b[33mDisconnected\x1b[0m\r\n');
64
- };
65
-
66
- term.onData((data) => {
67
- if (socket.readyState === WebSocket.OPEN) {
68
- socket.send(`exec:${data}`);
69
- }
70
- });
71
-
72
- const newTab: Tab = { id, terminal: term, socket, fitAddon };
73
- setTabs((prev) => [...prev, newTab]);
74
- setActiveTab(id);
75
- return newTab;
76
- }, []);
77
-
78
- // Create initial terminal
79
- useEffect(() => {
80
- if (tabs.length === 0) {
81
- createTerminal();
82
- }
83
- // eslint-disable-next-line react-hooks/exhaustive-deps
84
- }, []);
85
-
86
- // Mount terminal to DOM when active tab changes
87
- useEffect(() => {
88
- const tab = tabs.find((t) => t.id === activeTab);
89
- if (!tab) return;
90
-
91
- const container = termRefs.current.get(tab.id);
92
- if (!container) return;
93
-
94
- // Only open if not already open
95
- if (!tab.terminal.element) {
96
- tab.terminal.open(container);
97
- tab.fitAddon.fit();
98
-
99
- // Handle resize
100
- const handleResize = () => {
101
- tab.fitAddon.fit();
102
- };
103
- window.addEventListener('resize', handleResize);
104
-
105
- return () => {
106
- window.removeEventListener('resize', handleResize);
107
- };
108
- }
109
- }, [activeTab, tabs]);
110
-
111
- const removeTab = (id: number) => {
112
- const tab = tabs.find((t) => t.id === id);
113
- if (!tab) return;
114
-
115
- // Clean up
116
- if (tab.socket.readyState === WebSocket.OPEN || tab.socket.readyState === WebSocket.CONNECTING) {
117
- tab.socket.close();
118
- }
119
- tab.terminal.dispose();
120
-
121
- setTabs((prev) => prev.filter((t) => t.id !== id));
122
-
123
- if (activeTab === id) {
124
- const remaining = tabs.filter((t) => t.id !== id);
125
- if (remaining.length > 0) {
126
- setActiveTab(remaining[0].id);
127
- }
128
- }
129
- };
130
-
131
- const activeTabIndex = tabs.findIndex((t) => t.id === activeTab);
132
-
133
- return (
134
- <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
135
- {/* Tab Bar */}
136
- <div
137
- style={{
138
- display: 'flex',
139
- alignItems: 'center',
140
- background: '#161b22',
141
- borderBottom: '1px solid #30363d',
142
- overflowX: 'auto',
143
- }}
144
- >
145
- {tabs.map((tab, idx) => (
146
- <div
147
- key={tab.id}
148
- onClick={() => setActiveTab(tab.id)}
149
- style={{
150
- padding: '6px 14px',
151
- cursor: 'pointer',
152
- background: activeTab === tab.id ? '#0d1117' : 'transparent',
153
- borderRight: '1px solid #30363d',
154
- display: 'flex',
155
- alignItems: 'center',
156
- gap: 8,
157
- fontSize: 12,
158
- color: activeTab === tab.id ? '#c9d1d9' : '#8b949e',
159
- whiteSpace: 'nowrap',
160
- userSelect: 'none',
161
- }}
162
- >
163
- <span>Terminal {idx + 1}</span>
164
- <span
165
- onClick={(e) => {
166
- e.stopPropagation();
167
- removeTab(tab.id);
168
- }}
169
- style={{
170
- opacity: 0.5,
171
- cursor: 'pointer',
172
- fontSize: 14,
173
- lineHeight: 1,
174
- }}
175
- onMouseEnter={(e) => (e.currentTarget.style.opacity = '1')}
176
- onMouseLeave={(e) => (e.currentTarget.style.opacity = '0.5')}
177
- >
178
- ×
179
- </span>
180
- </div>
181
- ))}
182
- <button
183
- onClick={() => createTerminal()}
184
- style={{
185
- padding: '6px 12px',
186
- background: 'transparent',
187
- border: 'none',
188
- color: '#8b949e',
189
- cursor: 'pointer',
190
- fontSize: 16,
191
- lineHeight: 1,
192
- }}
193
- title="New Terminal"
194
- >
195
- +
196
- </button>
197
- </div>
198
-
199
- {/* Terminal Containers */}
200
- <div style={{ flex: 1, position: 'relative', background: '#0d1117' }}>
201
- {tabs.map((tab) => (
202
- <div
203
- key={tab.id}
204
- ref={(el) => {
205
- if (el) termRefs.current.set(tab.id, el);
206
- }}
207
- style={{
208
- position: 'absolute',
209
- top: 0,
210
- left: 0,
211
- right: 0,
212
- bottom: 0,
213
- display: activeTab === tab.id ? 'block' : 'none',
214
- padding: 4,
215
- }}
216
- />
217
- ))}
218
- {tabs.length === 0 && (
219
- <div
220
- style={{
221
- display: 'flex',
222
- alignItems: 'center',
223
- justifyContent: 'center',
224
- height: '100%',
225
- color: '#8b949e',
226
- fontSize: 14,
227
- }}
228
- >
229
- No terminals open. Click + to create one.
230
- </div>
231
- )}
232
- </div>
233
- </div>
234
- );
235
- };
236
-
237
- export default TerminalTabs;
238
- '''
239
-
240
- with open('/mnt/agents/output/Terminal.tsx', 'w') as f:
241
- f.write(terminal_tsx)
242
- print("Terminal.tsx fixed")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/components/TerminalTabs.tsx DELETED
@@ -1,171 +0,0 @@
1
- import React, { useState, useRef, useEffect } from 'react';
2
- import { Terminal as XTerm } from 'xterm';
3
- import { FitAddon } from 'xterm-addon-fit';
4
- import 'xterm/css/xterm.css';
5
- import { FaPlus, FaTimes, FaRobot } from 'react-icons/fa';
6
- import axios from 'axios';
7
-
8
- interface Tab {
9
- id: number;
10
- terminal: XTerm;
11
- socket: WebSocket;
12
- }
13
-
14
- const TerminalTabs: React.FC = () => {
15
- const [tabs, setTabs] = useState<Tab[]>([]);
16
- const [activeTab, setActiveTab] = useState(0);
17
- const termRefs = useRef<Map<number, HTMLDivElement>>(new Map());
18
- const nextId = useRef(1);
19
- const [suggestionInput, setSuggestionInput] = useState('');
20
- const [suggesting, setSuggesting] = useState(false);
21
-
22
- const createTerminal = () => {
23
- const id = nextId.current++;
24
- const term = new XTerm({
25
- cursorBlink: true,
26
- theme: {
27
- background: '#0d1117',
28
- foreground: '#c9d1d9',
29
- cursor: '#58a6ff',
30
- black: '#484f58',
31
- red: '#f85149',
32
- green: '#3fb950',
33
- yellow: '#d29922',
34
- blue: '#58a6ff',
35
- magenta: '#bc8cff',
36
- cyan: '#39c5cf',
37
- white: '#c9d1d9',
38
- },
39
- fontSize: 13,
40
- fontFamily: 'monospace',
41
- });
42
- const fit = new FitAddon();
43
- term.loadAddon(fit);
44
- const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
45
- const socket = new WebSocket(`${protocol}//${window.location.host}/ws/terminal`);
46
- term.onData(data => socket.send(`exec:${data}`));
47
- socket.onmessage = (event) => term.write(event.data);
48
- const newTab: Tab = { id, terminal: term, socket };
49
- setTabs(prev => [...prev, newTab]);
50
- setActiveTab(id);
51
- return { term, socket, fit };
52
- };
53
-
54
- useEffect(() => { createTerminal(); }, []);
55
-
56
- useEffect(() => {
57
- const tab = tabs.find(t => t.id === activeTab);
58
- if (tab) {
59
- const container = termRefs.current.get(tab.id);
60
- if (container && !tab.terminal.element) {
61
- tab.terminal.open(container);
62
- const fitAddon = new FitAddon();
63
- tab.terminal.loadAddon(fitAddon);
64
- fitAddon.fit();
65
- window.addEventListener('resize', () => fitAddon.fit());
66
- }
67
- }
68
- }, [activeTab, tabs]);
69
-
70
- const removeTab = (id: number) => {
71
- const tab = tabs.find(t => t.id === id);
72
- if (tab) {
73
- tab.socket.close();
74
- tab.terminal.dispose();
75
- setTabs(prev => prev.filter(t => t.id !== id));
76
- if (activeTab === id) {
77
- const remaining = tabs.filter(t => t.id !== id);
78
- if (remaining.length > 0) setActiveTab(remaining[0].id);
79
- }
80
- }
81
- };
82
-
83
- const handleSuggestion = async () => {
84
- if (!suggestionInput.trim()) return;
85
- setSuggesting(true);
86
- try {
87
- const resp = await fetch('/api/chat', {
88
- method: 'POST',
89
- headers: { 'Content-Type': 'application/json' },
90
- body: JSON.stringify({
91
- model: 'groq-llama-3.1-8b-instant',
92
- messages: [{ role: 'user', content: `Suggest a terminal command for: ${suggestionInput}. Only respond with the command, nothing else.` }],
93
- project_path: '/app/sandbox',
94
- }),
95
- });
96
- const reader = resp.body?.getReader();
97
- let command = '';
98
- while (reader) {
99
- const { done, value } = await reader.read();
100
- if (done) break;
101
- const text = new TextDecoder().decode(value);
102
- const lines = text.split('\n').filter(l => l.startsWith('data: ')).map(l => l.slice(6));
103
- for (const line of lines) {
104
- if (line === '[DONE]') break;
105
- try {
106
- const { token } = JSON.parse(line);
107
- command += token;
108
- } catch (e) {}
109
- }
110
- }
111
- const tab = tabs.find(t => t.id === activeTab);
112
- if (tab) {
113
- tab.terminal.write(command + '\r\n');
114
- }
115
- } catch (e) {}
116
- setSuggesting(false);
117
- setSuggestionInput('');
118
- };
119
-
120
- return (
121
- <div style={{ height: '100%', display: 'flex', flexDirection: 'column', background: '#0d1117' }}>
122
- <div style={{ display: 'flex', background: 'var(--bg-tertiary)', borderBottom: '1px solid var(--border)', alignItems: 'center' }}>
123
- {tabs.map(tab => (
124
- <div key={tab.id}
125
- onClick={() => setActiveTab(tab.id)}
126
- style={{
127
- padding: '4px 12px',
128
- cursor: 'pointer',
129
- background: activeTab === tab.id ? 'var(--bg-primary)' : 'transparent',
130
- borderRight: '1px solid var(--border)',
131
- display: 'flex',
132
- alignItems: 'center',
133
- gap: 6,
134
- fontSize: 12,
135
- color: activeTab === tab.id ? 'var(--text-primary)' : 'var(--text-secondary)',
136
- }}>
137
- <span>Terminal {tab.id}</span>
138
- <FaTimes size={10} onClick={(e) => { e.stopPropagation(); removeTab(tab.id); }} style={{ opacity: 0.6 }} />
139
- </div>
140
- ))}
141
- <button onClick={() => createTerminal()} style={{ padding: '4px 8px', color: 'var(--text-secondary)' }}>
142
- <FaPlus size={12} />
143
- </button>
144
- <div style={{ flex: 1 }} />
145
- <div style={{ display: 'flex', alignItems: 'center', gap: 4, paddingRight: 8 }}>
146
- <input
147
- style={{ width: 150, fontSize: 12, padding: '2px 6px' }}
148
- placeholder="Describe command..."
149
- value={suggestionInput}
150
- onChange={e => setSuggestionInput(e.target.value)}
151
- onKeyDown={e => e.key === 'Enter' && handleSuggestion()}
152
- />
153
- <button onClick={handleSuggestion} disabled={suggesting} style={{ color: 'var(--accent)', fontSize: 12 }}>
154
- <FaRobot /> {suggesting ? '...' : 'Suggest'}
155
- </button>
156
- </div>
157
- </div>
158
- <div style={{ flex: 1, position: 'relative' }}>
159
- {tabs.map(tab => (
160
- <div
161
- key={tab.id}
162
- ref={el => { if (el) termRefs.current.set(tab.id, el); }}
163
- style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, display: activeTab === tab.id ? 'block' : 'none' }}
164
- />
165
- ))}
166
- </div>
167
- </div>
168
- );
169
- };
170
-
171
- export default TerminalTabs;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/components/TopBar.tsx DELETED
@@ -1,20 +0,0 @@
1
- import React from 'react';
2
- import { FaCode, FaGithub, FaRocket } from 'react-icons/fa';
3
-
4
- const TopBar: React.FC = () => {
5
- return (
6
- <div style={{ height: 36, background: 'var(--bg-secondary)', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 12px', fontSize: 13 }}>
7
- <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
8
- <FaCode style={{ color: 'var(--accent)' }} />
9
- <span style={{ fontWeight: 600 }}>Codeki</span>
10
- <span style={{ color: 'var(--text-secondary)' }}>– AI Coding Assistant</span>
11
- </div>
12
- <div style={{ display: 'flex', gap: 15, alignItems: 'center' }}>
13
- <FaGithub style={{ color: 'var(--text-secondary)', cursor: 'pointer' }} />
14
- <FaRocket style={{ color: 'var(--accent)', cursor: 'pointer' }} />
15
- </div>
16
- </div>
17
- );
18
- };
19
-
20
- export default TopBar;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/index.css DELETED
@@ -1,25 +0,0 @@
1
- :root {
2
- --bg-primary: #0d1117;
3
- --bg-secondary: #161b22;
4
- --bg-tertiary: #21262d;
5
- --border: #30363d;
6
- --text-primary: #c9d1d9;
7
- --text-secondary: #8b949e;
8
- --accent: #58a6ff;
9
- --accent-hover: #79c0ff;
10
- --danger: #f85149;
11
- --success: #3fb950;
12
- --warning: #d29922;
13
- }
14
- * { margin: 0; padding: 0; box-sizing: border-box; }
15
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', sans-serif; background: var(--bg-primary); color: var(--text-primary); overflow: hidden; }
16
- #root { height: 100vh; }
17
- ::-webkit-scrollbar { width: 8px; height: 8px; }
18
- ::-webkit-scrollbar-track { background: var(--bg-secondary); }
19
- ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
20
- ::-webkit-scrollbar-thumb:hover { background: #484f58; }
21
- button { cursor: pointer; border: none; background: none; color: inherit; font-family: inherit; }
22
- input, select, textarea { background: var(--bg-tertiary); border: 1px solid var(--border); color: var(--text-primary); padding: 6px 10px; border-radius: 6px; font-family: inherit; outline: none; transition: border 0.2s; }
23
- input:focus, select:focus, textarea:focus { border-color: var(--accent); }
24
- .spin { animation: spin 1s linear infinite; }
25
- @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/index.tsx DELETED
@@ -1,13 +0,0 @@
1
- import React from 'react';
2
- import ReactDOM from 'react-dom/client';
3
- import './index.css';
4
- import App from './App';
5
-
6
- const root = ReactDOM.createRoot(
7
- document.getElementById('root') as HTMLElement
8
- );
9
- root.render(
10
- <React.StrictMode>
11
- <App />
12
- </React.StrictMode>
13
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/services/api.ts DELETED
@@ -1,2 +0,0 @@
1
- // Placeholder for API calls
2
- export const apiBase = '';