@siteboon/claude-code-ui 1.8.2 → 1.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/index-CeR_JfKq.js +895 -0
- package/dist/assets/index-Co7ALK3i.css +32 -0
- package/{index.html → dist/index.html} +2 -1
- package/package.json +6 -1
- package/server/database/auth.db +0 -0
- package/.env.example +0 -12
- package/.nvmrc +0 -1
- package/postcss.config.js +0 -6
- package/src/App.jsx +0 -751
- package/src/components/ChatInterface.jsx +0 -3485
- package/src/components/ClaudeLogo.jsx +0 -11
- package/src/components/ClaudeStatus.jsx +0 -107
- package/src/components/CodeEditor.jsx +0 -422
- package/src/components/CreateTaskModal.jsx +0 -88
- package/src/components/CursorLogo.jsx +0 -9
- package/src/components/DarkModeToggle.jsx +0 -35
- package/src/components/DiffViewer.jsx +0 -41
- package/src/components/ErrorBoundary.jsx +0 -73
- package/src/components/FileTree.jsx +0 -480
- package/src/components/GitPanel.jsx +0 -1283
- package/src/components/ImageViewer.jsx +0 -54
- package/src/components/LoginForm.jsx +0 -110
- package/src/components/MainContent.jsx +0 -577
- package/src/components/MicButton.jsx +0 -272
- package/src/components/MobileNav.jsx +0 -88
- package/src/components/NextTaskBanner.jsx +0 -695
- package/src/components/PRDEditor.jsx +0 -871
- package/src/components/ProtectedRoute.jsx +0 -44
- package/src/components/QuickSettingsPanel.jsx +0 -262
- package/src/components/Settings.jsx +0 -2023
- package/src/components/SetupForm.jsx +0 -135
- package/src/components/Shell.jsx +0 -663
- package/src/components/Sidebar.jsx +0 -1665
- package/src/components/StandaloneShell.jsx +0 -106
- package/src/components/TaskCard.jsx +0 -210
- package/src/components/TaskDetail.jsx +0 -406
- package/src/components/TaskIndicator.jsx +0 -108
- package/src/components/TaskList.jsx +0 -1054
- package/src/components/TaskMasterSetupWizard.jsx +0 -603
- package/src/components/TaskMasterStatus.jsx +0 -86
- package/src/components/TodoList.jsx +0 -91
- package/src/components/Tooltip.jsx +0 -91
- package/src/components/ui/badge.jsx +0 -31
- package/src/components/ui/button.jsx +0 -46
- package/src/components/ui/input.jsx +0 -19
- package/src/components/ui/scroll-area.jsx +0 -23
- package/src/contexts/AuthContext.jsx +0 -158
- package/src/contexts/TaskMasterContext.jsx +0 -324
- package/src/contexts/TasksSettingsContext.jsx +0 -95
- package/src/contexts/ThemeContext.jsx +0 -94
- package/src/contexts/WebSocketContext.jsx +0 -29
- package/src/hooks/useAudioRecorder.js +0 -109
- package/src/hooks/useVersionCheck.js +0 -39
- package/src/index.css +0 -822
- package/src/lib/utils.js +0 -6
- package/src/main.jsx +0 -10
- package/src/utils/api.js +0 -141
- package/src/utils/websocket.js +0 -109
- package/src/utils/whisper.js +0 -37
- package/tailwind.config.js +0 -63
- package/vite.config.js +0 -29
- /package/{public → dist}/convert-icons.md +0 -0
- /package/{public → dist}/favicon.png +0 -0
- /package/{public → dist}/favicon.svg +0 -0
- /package/{public → dist}/generate-icons.js +0 -0
- /package/{public → dist}/icons/claude-ai-icon.svg +0 -0
- /package/{public → dist}/icons/cursor.svg +0 -0
- /package/{public → dist}/icons/generate-icons.md +0 -0
- /package/{public → dist}/icons/icon-128x128.png +0 -0
- /package/{public → dist}/icons/icon-128x128.svg +0 -0
- /package/{public → dist}/icons/icon-144x144.png +0 -0
- /package/{public → dist}/icons/icon-144x144.svg +0 -0
- /package/{public → dist}/icons/icon-152x152.png +0 -0
- /package/{public → dist}/icons/icon-152x152.svg +0 -0
- /package/{public → dist}/icons/icon-192x192.png +0 -0
- /package/{public → dist}/icons/icon-192x192.svg +0 -0
- /package/{public → dist}/icons/icon-384x384.png +0 -0
- /package/{public → dist}/icons/icon-384x384.svg +0 -0
- /package/{public → dist}/icons/icon-512x512.png +0 -0
- /package/{public → dist}/icons/icon-512x512.svg +0 -0
- /package/{public → dist}/icons/icon-72x72.png +0 -0
- /package/{public → dist}/icons/icon-72x72.svg +0 -0
- /package/{public → dist}/icons/icon-96x96.png +0 -0
- /package/{public → dist}/icons/icon-96x96.svg +0 -0
- /package/{public → dist}/icons/icon-template.svg +0 -0
- /package/{public → dist}/logo.svg +0 -0
- /package/{public → dist}/manifest.json +0 -0
- /package/{public → dist}/screenshots/cli-selection.png +0 -0
- /package/{public → dist}/screenshots/desktop-main.png +0 -0
- /package/{public → dist}/screenshots/mobile-chat.png +0 -0
- /package/{public → dist}/screenshots/tools-modal.png +0 -0
- /package/{public → dist}/sw.js +0 -0
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import { cn } from '../lib/utils';
|
|
3
|
-
|
|
4
|
-
function ClaudeStatus({ status, onAbort, isLoading, provider = 'claude' }) {
|
|
5
|
-
const [elapsedTime, setElapsedTime] = useState(0);
|
|
6
|
-
const [animationPhase, setAnimationPhase] = useState(0);
|
|
7
|
-
const [fakeTokens, setFakeTokens] = useState(0);
|
|
8
|
-
|
|
9
|
-
// Update elapsed time every second
|
|
10
|
-
useEffect(() => {
|
|
11
|
-
if (!isLoading) {
|
|
12
|
-
setElapsedTime(0);
|
|
13
|
-
setFakeTokens(0);
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const startTime = Date.now();
|
|
18
|
-
const timer = setInterval(() => {
|
|
19
|
-
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
20
|
-
setElapsedTime(elapsed);
|
|
21
|
-
// Simulate token count increasing over time (roughly 30-50 tokens per second)
|
|
22
|
-
setFakeTokens(Math.floor(elapsed * (30 + Math.random() * 20)));
|
|
23
|
-
}, 1000);
|
|
24
|
-
|
|
25
|
-
return () => clearInterval(timer);
|
|
26
|
-
}, [isLoading]);
|
|
27
|
-
|
|
28
|
-
// Animate the status indicator
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
if (!isLoading) return;
|
|
31
|
-
|
|
32
|
-
const timer = setInterval(() => {
|
|
33
|
-
setAnimationPhase(prev => (prev + 1) % 4);
|
|
34
|
-
}, 500);
|
|
35
|
-
|
|
36
|
-
return () => clearInterval(timer);
|
|
37
|
-
}, [isLoading]);
|
|
38
|
-
|
|
39
|
-
if (!isLoading) return null;
|
|
40
|
-
|
|
41
|
-
// Clever action words that cycle
|
|
42
|
-
const actionWords = ['Thinking', 'Processing', 'Analyzing', 'Working', 'Computing', 'Reasoning'];
|
|
43
|
-
const actionIndex = Math.floor(elapsedTime / 3) % actionWords.length;
|
|
44
|
-
|
|
45
|
-
// Parse status data
|
|
46
|
-
const statusText = status?.text || actionWords[actionIndex];
|
|
47
|
-
const tokens = status?.tokens || fakeTokens;
|
|
48
|
-
const canInterrupt = status?.can_interrupt !== false;
|
|
49
|
-
|
|
50
|
-
// Animation characters
|
|
51
|
-
const spinners = ['✻', '✹', '✸', '✶'];
|
|
52
|
-
const currentSpinner = spinners[animationPhase];
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<div className="w-full mb-6 animate-in slide-in-from-bottom duration-300">
|
|
56
|
-
<div className="flex items-center justify-between max-w-4xl mx-auto bg-gray-900 dark:bg-gray-950 text-white rounded-lg shadow-lg px-4 py-3">
|
|
57
|
-
<div className="flex-1">
|
|
58
|
-
<div className="flex items-center gap-3">
|
|
59
|
-
{/* Animated spinner */}
|
|
60
|
-
<span className={cn(
|
|
61
|
-
"text-xl transition-all duration-500",
|
|
62
|
-
animationPhase % 2 === 0 ? "text-blue-400 scale-110" : "text-blue-300"
|
|
63
|
-
)}>
|
|
64
|
-
{currentSpinner}
|
|
65
|
-
</span>
|
|
66
|
-
|
|
67
|
-
{/* Status text - first line */}
|
|
68
|
-
<div className="flex-1">
|
|
69
|
-
<div className="flex items-center gap-2">
|
|
70
|
-
<span className="font-medium text-sm">{statusText}...</span>
|
|
71
|
-
<span className="text-gray-400 text-sm">({elapsedTime}s)</span>
|
|
72
|
-
{tokens > 0 && (
|
|
73
|
-
<>
|
|
74
|
-
<span className="text-gray-400">·</span>
|
|
75
|
-
<span className="text-gray-300 text-sm hidden sm:inline">⚒ {tokens.toLocaleString()} tokens</span>
|
|
76
|
-
<span className="text-gray-300 text-sm sm:hidden">⚒ {tokens.toLocaleString()}</span>
|
|
77
|
-
</>
|
|
78
|
-
)}
|
|
79
|
-
<span className="text-gray-400 hidden sm:inline">·</span>
|
|
80
|
-
<span className="text-gray-300 text-sm hidden sm:inline">esc to interrupt</span>
|
|
81
|
-
</div>
|
|
82
|
-
{/* Second line for mobile */}
|
|
83
|
-
<div className="text-xs text-gray-400 sm:hidden mt-1">
|
|
84
|
-
esc to interrupt
|
|
85
|
-
</div>
|
|
86
|
-
</div>
|
|
87
|
-
</div>
|
|
88
|
-
</div>
|
|
89
|
-
|
|
90
|
-
{/* Interrupt button */}
|
|
91
|
-
{canInterrupt && onAbort && (
|
|
92
|
-
<button
|
|
93
|
-
onClick={onAbort}
|
|
94
|
-
className="ml-3 text-xs bg-red-600 hover:bg-red-700 text-white px-2.5 py-1 sm:px-3 sm:py-1.5 rounded-md transition-colors flex items-center gap-1.5 flex-shrink-0"
|
|
95
|
-
>
|
|
96
|
-
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
97
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
98
|
-
</svg>
|
|
99
|
-
<span className="hidden sm:inline">Stop</span>
|
|
100
|
-
</button>
|
|
101
|
-
)}
|
|
102
|
-
</div>
|
|
103
|
-
</div>
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export default ClaudeStatus;
|
|
@@ -1,422 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect, useRef } from 'react';
|
|
2
|
-
import CodeMirror from '@uiw/react-codemirror';
|
|
3
|
-
import { javascript } from '@codemirror/lang-javascript';
|
|
4
|
-
import { python } from '@codemirror/lang-python';
|
|
5
|
-
import { html } from '@codemirror/lang-html';
|
|
6
|
-
import { css } from '@codemirror/lang-css';
|
|
7
|
-
import { json } from '@codemirror/lang-json';
|
|
8
|
-
import { markdown } from '@codemirror/lang-markdown';
|
|
9
|
-
import { oneDark } from '@codemirror/theme-one-dark';
|
|
10
|
-
import { EditorView, Decoration } from '@codemirror/view';
|
|
11
|
-
import { StateField, StateEffect, RangeSetBuilder } from '@codemirror/state';
|
|
12
|
-
import { X, Save, Download, Maximize2, Minimize2, Eye, EyeOff } from 'lucide-react';
|
|
13
|
-
import { api } from '../utils/api';
|
|
14
|
-
|
|
15
|
-
function CodeEditor({ file, onClose, projectPath }) {
|
|
16
|
-
const [content, setContent] = useState('');
|
|
17
|
-
const [loading, setLoading] = useState(true);
|
|
18
|
-
const [saving, setSaving] = useState(false);
|
|
19
|
-
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
20
|
-
const [isDarkMode, setIsDarkMode] = useState(true);
|
|
21
|
-
const [saveSuccess, setSaveSuccess] = useState(false);
|
|
22
|
-
const [showDiff, setShowDiff] = useState(!!file.diffInfo);
|
|
23
|
-
const [wordWrap, setWordWrap] = useState(false);
|
|
24
|
-
|
|
25
|
-
// Create diff highlighting
|
|
26
|
-
const diffEffect = StateEffect.define();
|
|
27
|
-
|
|
28
|
-
const diffField = StateField.define({
|
|
29
|
-
create() {
|
|
30
|
-
return Decoration.none;
|
|
31
|
-
},
|
|
32
|
-
update(decorations, tr) {
|
|
33
|
-
decorations = decorations.map(tr.changes);
|
|
34
|
-
|
|
35
|
-
for (let effect of tr.effects) {
|
|
36
|
-
if (effect.is(diffEffect)) {
|
|
37
|
-
decorations = effect.value;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return decorations;
|
|
41
|
-
},
|
|
42
|
-
provide: f => EditorView.decorations.from(f)
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const createDiffDecorations = (content, diffInfo) => {
|
|
46
|
-
if (!diffInfo || !showDiff) return Decoration.none;
|
|
47
|
-
|
|
48
|
-
const builder = new RangeSetBuilder();
|
|
49
|
-
const lines = content.split('\n');
|
|
50
|
-
const oldLines = diffInfo.old_string.split('\n');
|
|
51
|
-
|
|
52
|
-
// Find the line where the old content starts
|
|
53
|
-
let startLineIndex = -1;
|
|
54
|
-
for (let i = 0; i <= lines.length - oldLines.length; i++) {
|
|
55
|
-
let matches = true;
|
|
56
|
-
for (let j = 0; j < oldLines.length; j++) {
|
|
57
|
-
if (lines[i + j] !== oldLines[j]) {
|
|
58
|
-
matches = false;
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
if (matches) {
|
|
63
|
-
startLineIndex = i;
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (startLineIndex >= 0) {
|
|
69
|
-
let pos = 0;
|
|
70
|
-
// Calculate position to start of old content
|
|
71
|
-
for (let i = 0; i < startLineIndex; i++) {
|
|
72
|
-
pos += lines[i].length + 1; // +1 for newline
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Highlight old lines (to be removed)
|
|
76
|
-
for (let i = 0; i < oldLines.length; i++) {
|
|
77
|
-
const lineStart = pos;
|
|
78
|
-
const lineEnd = pos + oldLines[i].length;
|
|
79
|
-
builder.add(lineStart, lineEnd, Decoration.line({
|
|
80
|
-
class: isDarkMode ? 'diff-removed-dark' : 'diff-removed-light'
|
|
81
|
-
}));
|
|
82
|
-
pos += oldLines[i].length + 1;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return builder.finish();
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
// Diff decoration theme
|
|
90
|
-
const diffTheme = EditorView.theme({
|
|
91
|
-
'.diff-removed-light': {
|
|
92
|
-
backgroundColor: '#fef2f2',
|
|
93
|
-
borderLeft: '3px solid #ef4444'
|
|
94
|
-
},
|
|
95
|
-
'.diff-removed-dark': {
|
|
96
|
-
backgroundColor: 'rgba(239, 68, 68, 0.1)',
|
|
97
|
-
borderLeft: '3px solid #ef4444'
|
|
98
|
-
},
|
|
99
|
-
'.diff-added-light': {
|
|
100
|
-
backgroundColor: '#f0fdf4',
|
|
101
|
-
borderLeft: '3px solid #22c55e'
|
|
102
|
-
},
|
|
103
|
-
'.diff-added-dark': {
|
|
104
|
-
backgroundColor: 'rgba(34, 197, 94, 0.1)',
|
|
105
|
-
borderLeft: '3px solid #22c55e'
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// Get language extension based on file extension
|
|
110
|
-
const getLanguageExtension = (filename) => {
|
|
111
|
-
const ext = filename.split('.').pop()?.toLowerCase();
|
|
112
|
-
switch (ext) {
|
|
113
|
-
case 'js':
|
|
114
|
-
case 'jsx':
|
|
115
|
-
case 'ts':
|
|
116
|
-
case 'tsx':
|
|
117
|
-
return [javascript({ jsx: true, typescript: ext.includes('ts') })];
|
|
118
|
-
case 'py':
|
|
119
|
-
return [python()];
|
|
120
|
-
case 'html':
|
|
121
|
-
case 'htm':
|
|
122
|
-
return [html()];
|
|
123
|
-
case 'css':
|
|
124
|
-
case 'scss':
|
|
125
|
-
case 'less':
|
|
126
|
-
return [css()];
|
|
127
|
-
case 'json':
|
|
128
|
-
return [json()];
|
|
129
|
-
case 'md':
|
|
130
|
-
case 'markdown':
|
|
131
|
-
return [markdown()];
|
|
132
|
-
default:
|
|
133
|
-
return [];
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
// Load file content
|
|
138
|
-
useEffect(() => {
|
|
139
|
-
const loadFileContent = async () => {
|
|
140
|
-
try {
|
|
141
|
-
setLoading(true);
|
|
142
|
-
|
|
143
|
-
const response = await api.readFile(file.projectName, file.path);
|
|
144
|
-
|
|
145
|
-
if (!response.ok) {
|
|
146
|
-
throw new Error(`Failed to load file: ${response.status} ${response.statusText}`);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const data = await response.json();
|
|
150
|
-
setContent(data.content);
|
|
151
|
-
} catch (error) {
|
|
152
|
-
console.error('Error loading file:', error);
|
|
153
|
-
setContent(`// Error loading file: ${error.message}\n// File: ${file.name}\n// Path: ${file.path}`);
|
|
154
|
-
} finally {
|
|
155
|
-
setLoading(false);
|
|
156
|
-
}
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
loadFileContent();
|
|
160
|
-
}, [file, projectPath]);
|
|
161
|
-
|
|
162
|
-
// Update diff decorations when content or diff info changes
|
|
163
|
-
const editorRef = useRef(null);
|
|
164
|
-
|
|
165
|
-
useEffect(() => {
|
|
166
|
-
if (editorRef.current && content && file.diffInfo && showDiff) {
|
|
167
|
-
const decorations = createDiffDecorations(content, file.diffInfo);
|
|
168
|
-
const view = editorRef.current.view;
|
|
169
|
-
if (view) {
|
|
170
|
-
view.dispatch({
|
|
171
|
-
effects: diffEffect.of(decorations)
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}, [content, file.diffInfo, showDiff, isDarkMode]);
|
|
176
|
-
|
|
177
|
-
const handleSave = async () => {
|
|
178
|
-
setSaving(true);
|
|
179
|
-
try {
|
|
180
|
-
const response = await api.saveFile(file.projectName, file.path, content);
|
|
181
|
-
|
|
182
|
-
if (!response.ok) {
|
|
183
|
-
const errorData = await response.json();
|
|
184
|
-
throw new Error(errorData.error || `Save failed: ${response.status}`);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const result = await response.json();
|
|
188
|
-
|
|
189
|
-
// Show success feedback
|
|
190
|
-
setSaveSuccess(true);
|
|
191
|
-
setTimeout(() => setSaveSuccess(false), 2000); // Hide after 2 seconds
|
|
192
|
-
|
|
193
|
-
} catch (error) {
|
|
194
|
-
console.error('Error saving file:', error);
|
|
195
|
-
alert(`Error saving file: ${error.message}`);
|
|
196
|
-
} finally {
|
|
197
|
-
setSaving(false);
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
const handleDownload = () => {
|
|
202
|
-
const blob = new Blob([content], { type: 'text/plain' });
|
|
203
|
-
const url = URL.createObjectURL(blob);
|
|
204
|
-
const a = document.createElement('a');
|
|
205
|
-
a.href = url;
|
|
206
|
-
a.download = file.name;
|
|
207
|
-
document.body.appendChild(a);
|
|
208
|
-
a.click();
|
|
209
|
-
document.body.removeChild(a);
|
|
210
|
-
URL.revokeObjectURL(url);
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
const toggleFullscreen = () => {
|
|
214
|
-
setIsFullscreen(!isFullscreen);
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
// Handle keyboard shortcuts
|
|
218
|
-
useEffect(() => {
|
|
219
|
-
const handleKeyDown = (e) => {
|
|
220
|
-
if (e.ctrlKey || e.metaKey) {
|
|
221
|
-
if (e.key === 's') {
|
|
222
|
-
e.preventDefault();
|
|
223
|
-
handleSave();
|
|
224
|
-
} else if (e.key === 'Escape') {
|
|
225
|
-
e.preventDefault();
|
|
226
|
-
onClose();
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
document.addEventListener('keydown', handleKeyDown);
|
|
232
|
-
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
233
|
-
}, [content]);
|
|
234
|
-
|
|
235
|
-
if (loading) {
|
|
236
|
-
return (
|
|
237
|
-
<>
|
|
238
|
-
<style>
|
|
239
|
-
{`
|
|
240
|
-
.code-editor-loading {
|
|
241
|
-
background-color: ${isDarkMode ? '#111827' : '#ffffff'} !important;
|
|
242
|
-
}
|
|
243
|
-
.code-editor-loading:hover {
|
|
244
|
-
background-color: ${isDarkMode ? '#111827' : '#ffffff'} !important;
|
|
245
|
-
}
|
|
246
|
-
`}
|
|
247
|
-
</style>
|
|
248
|
-
<div className="fixed inset-0 z-50 md:bg-black/50 md:flex md:items-center md:justify-center">
|
|
249
|
-
<div className="code-editor-loading w-full h-full md:rounded-lg md:w-auto md:h-auto p-8 flex items-center justify-center">
|
|
250
|
-
<div className="flex items-center gap-3">
|
|
251
|
-
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600"></div>
|
|
252
|
-
<span className="text-gray-900 dark:text-white">Loading {file.name}...</span>
|
|
253
|
-
</div>
|
|
254
|
-
</div>
|
|
255
|
-
</div>
|
|
256
|
-
</>
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return (
|
|
261
|
-
<div className={`fixed inset-0 z-50 ${
|
|
262
|
-
// Mobile: native fullscreen, Desktop: modal with backdrop
|
|
263
|
-
'md:bg-black/50 md:flex md:items-center md:justify-center md:p-4'
|
|
264
|
-
} ${isFullscreen ? 'md:p-0' : ''}`}>
|
|
265
|
-
<div className={`bg-white shadow-2xl flex flex-col ${
|
|
266
|
-
// Mobile: always fullscreen, Desktop: modal sizing
|
|
267
|
-
'w-full h-full md:rounded-lg md:shadow-2xl' +
|
|
268
|
-
(isFullscreen ? ' md:w-full md:h-full md:rounded-none' : ' md:w-full md:max-w-6xl md:h-[80vh] md:max-h-[80vh]')
|
|
269
|
-
}`}>
|
|
270
|
-
{/* Header */}
|
|
271
|
-
<div className="flex items-center justify-between p-4 border-b border-gray-200 flex-shrink-0 min-w-0">
|
|
272
|
-
<div className="flex items-center gap-3 min-w-0 flex-1">
|
|
273
|
-
<div className="w-8 h-8 bg-blue-600 rounded flex items-center justify-center flex-shrink-0">
|
|
274
|
-
<span className="text-white text-sm font-mono">
|
|
275
|
-
{file.name.split('.').pop()?.toUpperCase() || 'FILE'}
|
|
276
|
-
</span>
|
|
277
|
-
</div>
|
|
278
|
-
<div className="min-w-0 flex-1">
|
|
279
|
-
<div className="flex items-center gap-2 min-w-0">
|
|
280
|
-
<h3 className="font-medium text-gray-900 truncate">{file.name}</h3>
|
|
281
|
-
{file.diffInfo && (
|
|
282
|
-
<span className="text-xs bg-blue-100 text-blue-600 px-2 py-1 rounded whitespace-nowrap">
|
|
283
|
-
📝 Has changes
|
|
284
|
-
</span>
|
|
285
|
-
)}
|
|
286
|
-
</div>
|
|
287
|
-
<p className="text-sm text-gray-500 truncate">{file.path}</p>
|
|
288
|
-
</div>
|
|
289
|
-
</div>
|
|
290
|
-
|
|
291
|
-
<div className="flex items-center gap-1 md:gap-2 flex-shrink-0">
|
|
292
|
-
{file.diffInfo && (
|
|
293
|
-
<button
|
|
294
|
-
onClick={() => setShowDiff(!showDiff)}
|
|
295
|
-
className="p-2 md:p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 min-w-[44px] min-h-[44px] md:min-w-0 md:min-h-0 flex items-center justify-center"
|
|
296
|
-
title={showDiff ? "Hide diff highlighting" : "Show diff highlighting"}
|
|
297
|
-
>
|
|
298
|
-
{showDiff ? <EyeOff className="w-5 h-5 md:w-4 md:h-4" /> : <Eye className="w-5 h-5 md:w-4 md:h-4" />}
|
|
299
|
-
</button>
|
|
300
|
-
)}
|
|
301
|
-
|
|
302
|
-
<button
|
|
303
|
-
onClick={() => setWordWrap(!wordWrap)}
|
|
304
|
-
className={`p-2 md:p-2 rounded-md hover:bg-gray-100 min-w-[44px] min-h-[44px] md:min-w-0 md:min-h-0 flex items-center justify-center ${
|
|
305
|
-
wordWrap
|
|
306
|
-
? 'text-blue-600 bg-blue-50'
|
|
307
|
-
: 'text-gray-600 hover:text-gray-900'
|
|
308
|
-
}`}
|
|
309
|
-
title={wordWrap ? 'Disable word wrap' : 'Enable word wrap'}
|
|
310
|
-
>
|
|
311
|
-
<span className="text-sm md:text-xs font-mono font-bold">↵</span>
|
|
312
|
-
</button>
|
|
313
|
-
|
|
314
|
-
<button
|
|
315
|
-
onClick={() => setIsDarkMode(!isDarkMode)}
|
|
316
|
-
className="p-2 md:p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 min-w-[44px] min-h-[44px] md:min-w-0 md:min-h-0 flex items-center justify-center"
|
|
317
|
-
title="Toggle theme"
|
|
318
|
-
>
|
|
319
|
-
<span className="text-lg md:text-base">{isDarkMode ? '☀️' : '🌙'}</span>
|
|
320
|
-
</button>
|
|
321
|
-
|
|
322
|
-
<button
|
|
323
|
-
onClick={handleDownload}
|
|
324
|
-
className="p-2 md:p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 min-w-[44px] min-h-[44px] md:min-w-0 md:min-h-0 flex items-center justify-center"
|
|
325
|
-
title="Download file"
|
|
326
|
-
>
|
|
327
|
-
<Download className="w-5 h-5 md:w-4 md:h-4" />
|
|
328
|
-
</button>
|
|
329
|
-
|
|
330
|
-
<button
|
|
331
|
-
onClick={handleSave}
|
|
332
|
-
disabled={saving}
|
|
333
|
-
className={`px-3 py-2 text-white rounded-md disabled:opacity-50 flex items-center gap-2 transition-colors min-h-[44px] md:min-h-0 ${
|
|
334
|
-
saveSuccess
|
|
335
|
-
? 'bg-green-600 hover:bg-green-700'
|
|
336
|
-
: 'bg-blue-600 hover:bg-blue-700'
|
|
337
|
-
}`}
|
|
338
|
-
>
|
|
339
|
-
{saveSuccess ? (
|
|
340
|
-
<>
|
|
341
|
-
<svg className="w-5 h-5 md:w-4 md:h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
342
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
343
|
-
</svg>
|
|
344
|
-
<span className="hidden sm:inline">Saved!</span>
|
|
345
|
-
</>
|
|
346
|
-
) : (
|
|
347
|
-
<>
|
|
348
|
-
<Save className="w-5 h-5 md:w-4 md:h-4" />
|
|
349
|
-
<span className="hidden sm:inline">{saving ? 'Saving...' : 'Save'}</span>
|
|
350
|
-
</>
|
|
351
|
-
)}
|
|
352
|
-
</button>
|
|
353
|
-
|
|
354
|
-
<button
|
|
355
|
-
onClick={toggleFullscreen}
|
|
356
|
-
className="hidden md:flex p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 items-center justify-center"
|
|
357
|
-
title={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}
|
|
358
|
-
>
|
|
359
|
-
{isFullscreen ? <Minimize2 className="w-4 h-4" /> : <Maximize2 className="w-4 h-4" />}
|
|
360
|
-
</button>
|
|
361
|
-
|
|
362
|
-
<button
|
|
363
|
-
onClick={onClose}
|
|
364
|
-
className="p-2 md:p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 min-w-[44px] min-h-[44px] md:min-w-0 md:min-h-0 flex items-center justify-center"
|
|
365
|
-
title="Close"
|
|
366
|
-
>
|
|
367
|
-
<X className="w-6 h-6 md:w-4 md:h-4" />
|
|
368
|
-
</button>
|
|
369
|
-
</div>
|
|
370
|
-
</div>
|
|
371
|
-
|
|
372
|
-
{/* Editor */}
|
|
373
|
-
<div className="flex-1 overflow-hidden">
|
|
374
|
-
<CodeMirror
|
|
375
|
-
ref={editorRef}
|
|
376
|
-
value={content}
|
|
377
|
-
onChange={setContent}
|
|
378
|
-
extensions={[
|
|
379
|
-
...getLanguageExtension(file.name),
|
|
380
|
-
diffField,
|
|
381
|
-
diffTheme,
|
|
382
|
-
...(wordWrap ? [EditorView.lineWrapping] : [])
|
|
383
|
-
]}
|
|
384
|
-
theme={isDarkMode ? oneDark : undefined}
|
|
385
|
-
height="100%"
|
|
386
|
-
style={{
|
|
387
|
-
fontSize: '14px',
|
|
388
|
-
height: '100%',
|
|
389
|
-
}}
|
|
390
|
-
basicSetup={{
|
|
391
|
-
lineNumbers: true,
|
|
392
|
-
foldGutter: true,
|
|
393
|
-
dropCursor: false,
|
|
394
|
-
allowMultipleSelections: false,
|
|
395
|
-
indentOnInput: true,
|
|
396
|
-
bracketMatching: true,
|
|
397
|
-
closeBrackets: true,
|
|
398
|
-
autocompletion: true,
|
|
399
|
-
highlightSelectionMatches: true,
|
|
400
|
-
searchKeymap: true,
|
|
401
|
-
}}
|
|
402
|
-
/>
|
|
403
|
-
</div>
|
|
404
|
-
|
|
405
|
-
{/* Footer */}
|
|
406
|
-
<div className="flex items-center justify-between p-3 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 flex-shrink-0">
|
|
407
|
-
<div className="flex items-center gap-4 text-sm text-gray-600 dark:text-gray-400">
|
|
408
|
-
<span>Lines: {content.split('\n').length}</span>
|
|
409
|
-
<span>Characters: {content.length}</span>
|
|
410
|
-
<span>Language: {file.name.split('.').pop()?.toUpperCase() || 'Text'}</span>
|
|
411
|
-
</div>
|
|
412
|
-
|
|
413
|
-
<div className="text-sm text-gray-500 dark:text-gray-400">
|
|
414
|
-
Press Ctrl+S to save • Esc to close
|
|
415
|
-
</div>
|
|
416
|
-
</div>
|
|
417
|
-
</div>
|
|
418
|
-
</div>
|
|
419
|
-
);
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
export default CodeEditor;
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { X, Sparkles } from 'lucide-react';
|
|
3
|
-
|
|
4
|
-
const CreateTaskModal = ({ currentProject, onClose, onTaskCreated }) => {
|
|
5
|
-
|
|
6
|
-
return (
|
|
7
|
-
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4">
|
|
8
|
-
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md border border-gray-200 dark:border-gray-700">
|
|
9
|
-
{/* Header */}
|
|
10
|
-
<div className="flex items-center justify-between p-6 border-b border-gray-200 dark:border-gray-700">
|
|
11
|
-
<div className="flex items-center gap-3">
|
|
12
|
-
<div className="w-8 h-8 bg-blue-100 dark:bg-blue-900/50 rounded-lg flex items-center justify-center">
|
|
13
|
-
<Sparkles className="w-4 h-4 text-blue-600 dark:text-blue-400" />
|
|
14
|
-
</div>
|
|
15
|
-
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">Create AI-Generated Task</h3>
|
|
16
|
-
</div>
|
|
17
|
-
<button
|
|
18
|
-
onClick={onClose}
|
|
19
|
-
className="p-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700"
|
|
20
|
-
>
|
|
21
|
-
<X className="w-5 h-5" />
|
|
22
|
-
</button>
|
|
23
|
-
</div>
|
|
24
|
-
|
|
25
|
-
{/* Content */}
|
|
26
|
-
<div className="p-6 space-y-6">
|
|
27
|
-
{/* AI-First Approach */}
|
|
28
|
-
<div className="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4 border border-blue-200 dark:border-blue-800">
|
|
29
|
-
<div className="flex items-start gap-3">
|
|
30
|
-
<div className="w-8 h-8 bg-blue-100 dark:bg-blue-900/50 rounded-lg flex items-center justify-center flex-shrink-0 mt-0.5">
|
|
31
|
-
<Sparkles className="w-4 h-4 text-blue-600 dark:text-blue-400" />
|
|
32
|
-
</div>
|
|
33
|
-
<div className="flex-1">
|
|
34
|
-
<h4 className="font-semibold text-blue-900 dark:text-blue-100 mb-2">
|
|
35
|
-
💡 Pro Tip: Ask Claude Code Directly!
|
|
36
|
-
</h4>
|
|
37
|
-
<p className="text-sm text-blue-800 dark:text-blue-200 mb-3">
|
|
38
|
-
You can simply ask Claude Code in the chat to create tasks for you.
|
|
39
|
-
The AI assistant will automatically generate detailed tasks with research-backed insights.
|
|
40
|
-
</p>
|
|
41
|
-
|
|
42
|
-
<div className="bg-white dark:bg-gray-800 rounded border border-blue-200 dark:border-blue-700 p-3 mb-3">
|
|
43
|
-
<p className="text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Example:</p>
|
|
44
|
-
<p className="text-sm text-gray-900 dark:text-white font-mono">
|
|
45
|
-
"Please add a new task to implement user profile image uploads using Cloudinary, research the best approach."
|
|
46
|
-
</p>
|
|
47
|
-
</div>
|
|
48
|
-
|
|
49
|
-
<p className="text-xs text-blue-700 dark:text-blue-300">
|
|
50
|
-
<strong>This runs:</strong> <code className="bg-blue-100 dark:bg-blue-900/50 px-1 rounded text-xs">
|
|
51
|
-
task-master add-task --prompt="Implement user profile image uploads using Cloudinary" --research
|
|
52
|
-
</code>
|
|
53
|
-
</p>
|
|
54
|
-
</div>
|
|
55
|
-
</div>
|
|
56
|
-
</div>
|
|
57
|
-
|
|
58
|
-
{/* Learn More Link */}
|
|
59
|
-
<div className="text-center pt-4 border-t border-gray-200 dark:border-gray-700">
|
|
60
|
-
<p className="text-sm text-gray-600 dark:text-gray-400 mb-3">
|
|
61
|
-
For more examples and advanced usage patterns:
|
|
62
|
-
</p>
|
|
63
|
-
<a
|
|
64
|
-
href="https://github.com/eyaltoledano/claude-task-master/blob/main/docs/examples.md"
|
|
65
|
-
target="_blank"
|
|
66
|
-
rel="noopener noreferrer"
|
|
67
|
-
className="inline-block text-sm text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 underline font-medium"
|
|
68
|
-
>
|
|
69
|
-
View TaskMaster Documentation →
|
|
70
|
-
</a>
|
|
71
|
-
</div>
|
|
72
|
-
|
|
73
|
-
{/* Footer */}
|
|
74
|
-
<div className="pt-4">
|
|
75
|
-
<button
|
|
76
|
-
onClick={onClose}
|
|
77
|
-
className="w-full px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors"
|
|
78
|
-
>
|
|
79
|
-
Got it, I'll ask Claude Code directly
|
|
80
|
-
</button>
|
|
81
|
-
</div>
|
|
82
|
-
</div>
|
|
83
|
-
</div>
|
|
84
|
-
</div>
|
|
85
|
-
);
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
export default CreateTaskModal;
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { useTheme } from '../contexts/ThemeContext';
|
|
3
|
-
|
|
4
|
-
function DarkModeToggle() {
|
|
5
|
-
const { isDarkMode, toggleDarkMode } = useTheme();
|
|
6
|
-
|
|
7
|
-
return (
|
|
8
|
-
<button
|
|
9
|
-
onClick={toggleDarkMode}
|
|
10
|
-
className="relative inline-flex h-8 w-14 items-center rounded-full bg-gray-200 dark:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900"
|
|
11
|
-
role="switch"
|
|
12
|
-
aria-checked={isDarkMode}
|
|
13
|
-
aria-label="Toggle dark mode"
|
|
14
|
-
>
|
|
15
|
-
<span className="sr-only">Toggle dark mode</span>
|
|
16
|
-
<span
|
|
17
|
-
className={`${
|
|
18
|
-
isDarkMode ? 'translate-x-7' : 'translate-x-1'
|
|
19
|
-
} inline-block h-6 w-6 transform rounded-full bg-white shadow-lg transition-transform duration-200 flex items-center justify-center`}
|
|
20
|
-
>
|
|
21
|
-
{isDarkMode ? (
|
|
22
|
-
<svg className="w-3.5 h-3.5 text-gray-700" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
23
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
|
|
24
|
-
</svg>
|
|
25
|
-
) : (
|
|
26
|
-
<svg className="w-3.5 h-3.5 text-yellow-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
27
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
|
|
28
|
-
</svg>
|
|
29
|
-
)}
|
|
30
|
-
</span>
|
|
31
|
-
</button>
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export default DarkModeToggle;
|