@siteboon/claude-code-ui 1.8.2 → 1.8.3
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-BNGSzSdr.css +32 -0
- package/dist/assets/index-BZctMHnE.js +900 -0
- package/{index.html → dist/index.html} +4 -3
- 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,272 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect, useRef } from 'react';
|
|
2
|
-
import { Mic, Loader2, Brain } from 'lucide-react';
|
|
3
|
-
import { transcribeWithWhisper } from '../utils/whisper';
|
|
4
|
-
|
|
5
|
-
export function MicButton({ onTranscript, className = '' }) {
|
|
6
|
-
const [state, setState] = useState('idle'); // idle, recording, transcribing, processing
|
|
7
|
-
const [error, setError] = useState(null);
|
|
8
|
-
const [isSupported, setIsSupported] = useState(true);
|
|
9
|
-
|
|
10
|
-
const mediaRecorderRef = useRef(null);
|
|
11
|
-
const streamRef = useRef(null);
|
|
12
|
-
const chunksRef = useRef([]);
|
|
13
|
-
const lastTapRef = useRef(0);
|
|
14
|
-
|
|
15
|
-
// Check microphone support on mount
|
|
16
|
-
useEffect(() => {
|
|
17
|
-
const checkSupport = () => {
|
|
18
|
-
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
19
|
-
setIsSupported(false);
|
|
20
|
-
setError('Microphone not supported. Please use HTTPS or a modern browser.');
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Additional check for secure context
|
|
25
|
-
if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
|
|
26
|
-
setIsSupported(false);
|
|
27
|
-
setError('Microphone requires HTTPS. Please use a secure connection.');
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
setIsSupported(true);
|
|
32
|
-
setError(null);
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
checkSupport();
|
|
36
|
-
}, []);
|
|
37
|
-
|
|
38
|
-
// Start recording
|
|
39
|
-
const startRecording = async () => {
|
|
40
|
-
try {
|
|
41
|
-
console.log('Starting recording...');
|
|
42
|
-
setError(null);
|
|
43
|
-
chunksRef.current = [];
|
|
44
|
-
|
|
45
|
-
// Check if getUserMedia is available
|
|
46
|
-
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
47
|
-
throw new Error('Microphone access not available. Please use HTTPS or a supported browser.');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
51
|
-
streamRef.current = stream;
|
|
52
|
-
|
|
53
|
-
const mimeType = MediaRecorder.isTypeSupported('audio/webm') ? 'audio/webm' : 'audio/mp4';
|
|
54
|
-
const recorder = new MediaRecorder(stream, { mimeType });
|
|
55
|
-
mediaRecorderRef.current = recorder;
|
|
56
|
-
|
|
57
|
-
recorder.ondataavailable = (e) => {
|
|
58
|
-
if (e.data.size > 0) {
|
|
59
|
-
chunksRef.current.push(e.data);
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
recorder.onstop = async () => {
|
|
64
|
-
console.log('Recording stopped, creating blob...');
|
|
65
|
-
const blob = new Blob(chunksRef.current, { type: mimeType });
|
|
66
|
-
|
|
67
|
-
// Clean up stream
|
|
68
|
-
if (streamRef.current) {
|
|
69
|
-
streamRef.current.getTracks().forEach(track => track.stop());
|
|
70
|
-
streamRef.current = null;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Start transcribing
|
|
74
|
-
setState('transcribing');
|
|
75
|
-
|
|
76
|
-
// Check if we're in an enhancement mode
|
|
77
|
-
const whisperMode = window.localStorage.getItem('whisperMode') || 'default';
|
|
78
|
-
const isEnhancementMode = whisperMode === 'prompt' || whisperMode === 'vibe' || whisperMode === 'instructions' || whisperMode === 'architect';
|
|
79
|
-
|
|
80
|
-
// Set up a timer to switch to processing state for enhancement modes
|
|
81
|
-
let processingTimer;
|
|
82
|
-
if (isEnhancementMode) {
|
|
83
|
-
processingTimer = setTimeout(() => {
|
|
84
|
-
setState('processing');
|
|
85
|
-
}, 2000); // Switch to processing after 2 seconds
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
const text = await transcribeWithWhisper(blob);
|
|
90
|
-
if (text && onTranscript) {
|
|
91
|
-
onTranscript(text);
|
|
92
|
-
}
|
|
93
|
-
} catch (err) {
|
|
94
|
-
console.error('Transcription error:', err);
|
|
95
|
-
setError(err.message);
|
|
96
|
-
} finally {
|
|
97
|
-
if (processingTimer) {
|
|
98
|
-
clearTimeout(processingTimer);
|
|
99
|
-
}
|
|
100
|
-
setState('idle');
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
recorder.start();
|
|
105
|
-
setState('recording');
|
|
106
|
-
console.log('Recording started successfully');
|
|
107
|
-
} catch (err) {
|
|
108
|
-
console.error('Failed to start recording:', err);
|
|
109
|
-
|
|
110
|
-
// Provide specific error messages based on error type
|
|
111
|
-
let errorMessage = 'Microphone access failed';
|
|
112
|
-
|
|
113
|
-
if (err.name === 'NotAllowedError') {
|
|
114
|
-
errorMessage = 'Microphone access denied. Please allow microphone permissions.';
|
|
115
|
-
} else if (err.name === 'NotFoundError') {
|
|
116
|
-
errorMessage = 'No microphone found. Please check your audio devices.';
|
|
117
|
-
} else if (err.name === 'NotSupportedError') {
|
|
118
|
-
errorMessage = 'Microphone not supported by this browser.';
|
|
119
|
-
} else if (err.name === 'NotReadableError') {
|
|
120
|
-
errorMessage = 'Microphone is being used by another application.';
|
|
121
|
-
} else if (err.message.includes('HTTPS')) {
|
|
122
|
-
errorMessage = err.message;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
setError(errorMessage);
|
|
126
|
-
setState('idle');
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
// Stop recording
|
|
131
|
-
const stopRecording = () => {
|
|
132
|
-
console.log('Stopping recording...');
|
|
133
|
-
if (mediaRecorderRef.current && mediaRecorderRef.current.state === 'recording') {
|
|
134
|
-
mediaRecorderRef.current.stop();
|
|
135
|
-
// Don't set state here - let the onstop handler do it
|
|
136
|
-
} else {
|
|
137
|
-
// If recorder isn't in recording state, force cleanup
|
|
138
|
-
console.log('Recorder not in recording state, forcing cleanup');
|
|
139
|
-
if (streamRef.current) {
|
|
140
|
-
streamRef.current.getTracks().forEach(track => track.stop());
|
|
141
|
-
streamRef.current = null;
|
|
142
|
-
}
|
|
143
|
-
setState('idle');
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
// Handle button click
|
|
148
|
-
const handleClick = (e) => {
|
|
149
|
-
// Prevent double firing on mobile
|
|
150
|
-
if (e) {
|
|
151
|
-
e.preventDefault();
|
|
152
|
-
e.stopPropagation();
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Don't proceed if microphone is not supported
|
|
156
|
-
if (!isSupported) {
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Debounce for mobile double-tap issue
|
|
161
|
-
const now = Date.now();
|
|
162
|
-
if (now - lastTapRef.current < 300) {
|
|
163
|
-
console.log('Ignoring rapid tap');
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
lastTapRef.current = now;
|
|
167
|
-
|
|
168
|
-
console.log('Button clicked, current state:', state);
|
|
169
|
-
|
|
170
|
-
if (state === 'idle') {
|
|
171
|
-
startRecording();
|
|
172
|
-
} else if (state === 'recording') {
|
|
173
|
-
stopRecording();
|
|
174
|
-
}
|
|
175
|
-
// Do nothing if transcribing or processing
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
// Clean up on unmount
|
|
179
|
-
useEffect(() => {
|
|
180
|
-
return () => {
|
|
181
|
-
if (streamRef.current) {
|
|
182
|
-
streamRef.current.getTracks().forEach(track => track.stop());
|
|
183
|
-
}
|
|
184
|
-
};
|
|
185
|
-
}, []);
|
|
186
|
-
|
|
187
|
-
// Button appearance based on state
|
|
188
|
-
const getButtonAppearance = () => {
|
|
189
|
-
if (!isSupported) {
|
|
190
|
-
return {
|
|
191
|
-
icon: <Mic className="w-5 h-5" />,
|
|
192
|
-
className: 'bg-gray-400 cursor-not-allowed',
|
|
193
|
-
disabled: true
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
switch (state) {
|
|
198
|
-
case 'recording':
|
|
199
|
-
return {
|
|
200
|
-
icon: <Mic className="w-5 h-5 text-white" />,
|
|
201
|
-
className: 'bg-red-500 hover:bg-red-600 animate-pulse',
|
|
202
|
-
disabled: false
|
|
203
|
-
};
|
|
204
|
-
case 'transcribing':
|
|
205
|
-
return {
|
|
206
|
-
icon: <Loader2 className="w-5 h-5 animate-spin" />,
|
|
207
|
-
className: 'bg-blue-500 hover:bg-blue-600',
|
|
208
|
-
disabled: true
|
|
209
|
-
};
|
|
210
|
-
case 'processing':
|
|
211
|
-
return {
|
|
212
|
-
icon: <Brain className="w-5 h-5 animate-pulse" />,
|
|
213
|
-
className: 'bg-purple-500 hover:bg-purple-600',
|
|
214
|
-
disabled: true
|
|
215
|
-
};
|
|
216
|
-
default: // idle
|
|
217
|
-
return {
|
|
218
|
-
icon: <Mic className="w-5 h-5" />,
|
|
219
|
-
className: 'bg-gray-700 hover:bg-gray-600',
|
|
220
|
-
disabled: false
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
const { icon, className: buttonClass, disabled } = getButtonAppearance();
|
|
226
|
-
|
|
227
|
-
return (
|
|
228
|
-
<div className="relative">
|
|
229
|
-
<button
|
|
230
|
-
type="button"
|
|
231
|
-
style={{
|
|
232
|
-
backgroundColor: state === 'recording' ? '#ef4444' :
|
|
233
|
-
state === 'transcribing' ? '#3b82f6' :
|
|
234
|
-
state === 'processing' ? '#a855f7' :
|
|
235
|
-
'#374151'
|
|
236
|
-
}}
|
|
237
|
-
className={`
|
|
238
|
-
flex items-center justify-center
|
|
239
|
-
w-12 h-12 rounded-full
|
|
240
|
-
text-white transition-all duration-200
|
|
241
|
-
focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500
|
|
242
|
-
dark:ring-offset-gray-800
|
|
243
|
-
touch-action-manipulation
|
|
244
|
-
${disabled ? 'cursor-not-allowed opacity-75' : 'cursor-pointer'}
|
|
245
|
-
${state === 'recording' ? 'animate-pulse' : ''}
|
|
246
|
-
hover:opacity-90
|
|
247
|
-
${className}
|
|
248
|
-
`}
|
|
249
|
-
onClick={handleClick}
|
|
250
|
-
disabled={disabled}
|
|
251
|
-
>
|
|
252
|
-
{icon}
|
|
253
|
-
</button>
|
|
254
|
-
|
|
255
|
-
{error && (
|
|
256
|
-
<div className="absolute top-full mt-2 left-1/2 transform -translate-x-1/2
|
|
257
|
-
bg-red-500 text-white text-xs px-2 py-1 rounded whitespace-nowrap z-10
|
|
258
|
-
animate-fade-in">
|
|
259
|
-
{error}
|
|
260
|
-
</div>
|
|
261
|
-
)}
|
|
262
|
-
|
|
263
|
-
{state === 'recording' && (
|
|
264
|
-
<div className="absolute -inset-1 rounded-full border-2 border-red-500 animate-ping pointer-events-none" />
|
|
265
|
-
)}
|
|
266
|
-
|
|
267
|
-
{state === 'processing' && (
|
|
268
|
-
<div className="absolute -inset-1 rounded-full border-2 border-purple-500 animate-ping pointer-events-none" />
|
|
269
|
-
)}
|
|
270
|
-
</div>
|
|
271
|
-
);
|
|
272
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { MessageSquare, Folder, Terminal, GitBranch, Globe, CheckSquare } from 'lucide-react';
|
|
3
|
-
import { useTasksSettings } from '../contexts/TasksSettingsContext';
|
|
4
|
-
|
|
5
|
-
function MobileNav({ activeTab, setActiveTab, isInputFocused }) {
|
|
6
|
-
const { tasksEnabled } = useTasksSettings();
|
|
7
|
-
// Detect dark mode
|
|
8
|
-
const isDarkMode = document.documentElement.classList.contains('dark');
|
|
9
|
-
const navItems = [
|
|
10
|
-
{
|
|
11
|
-
id: 'chat',
|
|
12
|
-
icon: MessageSquare,
|
|
13
|
-
onClick: () => setActiveTab('chat')
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
id: 'shell',
|
|
17
|
-
icon: Terminal,
|
|
18
|
-
onClick: () => setActiveTab('shell')
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
id: 'files',
|
|
22
|
-
icon: Folder,
|
|
23
|
-
onClick: () => setActiveTab('files')
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
id: 'git',
|
|
27
|
-
icon: GitBranch,
|
|
28
|
-
onClick: () => setActiveTab('git')
|
|
29
|
-
},
|
|
30
|
-
// Conditionally add tasks tab if enabled
|
|
31
|
-
...(tasksEnabled ? [{
|
|
32
|
-
id: 'tasks',
|
|
33
|
-
icon: CheckSquare,
|
|
34
|
-
onClick: () => setActiveTab('tasks')
|
|
35
|
-
}] : [])
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<>
|
|
40
|
-
<style>
|
|
41
|
-
{`
|
|
42
|
-
.mobile-nav-container {
|
|
43
|
-
background-color: ${isDarkMode ? '#1f2937' : '#ffffff'} !important;
|
|
44
|
-
}
|
|
45
|
-
.mobile-nav-container:hover {
|
|
46
|
-
background-color: ${isDarkMode ? '#1f2937' : '#ffffff'} !important;
|
|
47
|
-
}
|
|
48
|
-
`}
|
|
49
|
-
</style>
|
|
50
|
-
<div
|
|
51
|
-
className={`mobile-nav-container fixed bottom-0 left-0 right-0 border-t border-gray-200 dark:border-gray-700 z-50 ios-bottom-safe transform transition-transform duration-300 ease-in-out shadow-lg ${
|
|
52
|
-
isInputFocused ? 'translate-y-full' : 'translate-y-0'
|
|
53
|
-
}`}
|
|
54
|
-
>
|
|
55
|
-
<div className="flex items-center justify-around py-1">
|
|
56
|
-
{navItems.map((item) => {
|
|
57
|
-
const Icon = item.icon;
|
|
58
|
-
const isActive = activeTab === item.id;
|
|
59
|
-
|
|
60
|
-
return (
|
|
61
|
-
<button
|
|
62
|
-
key={item.id}
|
|
63
|
-
onClick={item.onClick}
|
|
64
|
-
onTouchStart={(e) => {
|
|
65
|
-
e.preventDefault();
|
|
66
|
-
item.onClick();
|
|
67
|
-
}}
|
|
68
|
-
className={`flex items-center justify-center p-2 rounded-lg min-h-[40px] min-w-[40px] relative touch-manipulation ${
|
|
69
|
-
isActive
|
|
70
|
-
? 'text-blue-600 dark:text-blue-400'
|
|
71
|
-
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'
|
|
72
|
-
}`}
|
|
73
|
-
aria-label={item.id}
|
|
74
|
-
>
|
|
75
|
-
<Icon className="w-5 h-5" />
|
|
76
|
-
{isActive && (
|
|
77
|
-
<div className="absolute top-0 left-1/2 transform -translate-x-1/2 w-6 h-0.5 bg-blue-600 dark:bg-blue-400 rounded-full" />
|
|
78
|
-
)}
|
|
79
|
-
</button>
|
|
80
|
-
);
|
|
81
|
-
})}
|
|
82
|
-
</div>
|
|
83
|
-
</div>
|
|
84
|
-
</>
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export default MobileNav;
|