@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.
Files changed (92) hide show
  1. package/dist/assets/index-BNGSzSdr.css +32 -0
  2. package/dist/assets/index-BZctMHnE.js +900 -0
  3. package/{index.html → dist/index.html} +4 -3
  4. package/package.json +6 -1
  5. package/server/database/auth.db +0 -0
  6. package/.env.example +0 -12
  7. package/.nvmrc +0 -1
  8. package/postcss.config.js +0 -6
  9. package/src/App.jsx +0 -751
  10. package/src/components/ChatInterface.jsx +0 -3485
  11. package/src/components/ClaudeLogo.jsx +0 -11
  12. package/src/components/ClaudeStatus.jsx +0 -107
  13. package/src/components/CodeEditor.jsx +0 -422
  14. package/src/components/CreateTaskModal.jsx +0 -88
  15. package/src/components/CursorLogo.jsx +0 -9
  16. package/src/components/DarkModeToggle.jsx +0 -35
  17. package/src/components/DiffViewer.jsx +0 -41
  18. package/src/components/ErrorBoundary.jsx +0 -73
  19. package/src/components/FileTree.jsx +0 -480
  20. package/src/components/GitPanel.jsx +0 -1283
  21. package/src/components/ImageViewer.jsx +0 -54
  22. package/src/components/LoginForm.jsx +0 -110
  23. package/src/components/MainContent.jsx +0 -577
  24. package/src/components/MicButton.jsx +0 -272
  25. package/src/components/MobileNav.jsx +0 -88
  26. package/src/components/NextTaskBanner.jsx +0 -695
  27. package/src/components/PRDEditor.jsx +0 -871
  28. package/src/components/ProtectedRoute.jsx +0 -44
  29. package/src/components/QuickSettingsPanel.jsx +0 -262
  30. package/src/components/Settings.jsx +0 -2023
  31. package/src/components/SetupForm.jsx +0 -135
  32. package/src/components/Shell.jsx +0 -663
  33. package/src/components/Sidebar.jsx +0 -1665
  34. package/src/components/StandaloneShell.jsx +0 -106
  35. package/src/components/TaskCard.jsx +0 -210
  36. package/src/components/TaskDetail.jsx +0 -406
  37. package/src/components/TaskIndicator.jsx +0 -108
  38. package/src/components/TaskList.jsx +0 -1054
  39. package/src/components/TaskMasterSetupWizard.jsx +0 -603
  40. package/src/components/TaskMasterStatus.jsx +0 -86
  41. package/src/components/TodoList.jsx +0 -91
  42. package/src/components/Tooltip.jsx +0 -91
  43. package/src/components/ui/badge.jsx +0 -31
  44. package/src/components/ui/button.jsx +0 -46
  45. package/src/components/ui/input.jsx +0 -19
  46. package/src/components/ui/scroll-area.jsx +0 -23
  47. package/src/contexts/AuthContext.jsx +0 -158
  48. package/src/contexts/TaskMasterContext.jsx +0 -324
  49. package/src/contexts/TasksSettingsContext.jsx +0 -95
  50. package/src/contexts/ThemeContext.jsx +0 -94
  51. package/src/contexts/WebSocketContext.jsx +0 -29
  52. package/src/hooks/useAudioRecorder.js +0 -109
  53. package/src/hooks/useVersionCheck.js +0 -39
  54. package/src/index.css +0 -822
  55. package/src/lib/utils.js +0 -6
  56. package/src/main.jsx +0 -10
  57. package/src/utils/api.js +0 -141
  58. package/src/utils/websocket.js +0 -109
  59. package/src/utils/whisper.js +0 -37
  60. package/tailwind.config.js +0 -63
  61. package/vite.config.js +0 -29
  62. /package/{public → dist}/convert-icons.md +0 -0
  63. /package/{public → dist}/favicon.png +0 -0
  64. /package/{public → dist}/favicon.svg +0 -0
  65. /package/{public → dist}/generate-icons.js +0 -0
  66. /package/{public → dist}/icons/claude-ai-icon.svg +0 -0
  67. /package/{public → dist}/icons/cursor.svg +0 -0
  68. /package/{public → dist}/icons/generate-icons.md +0 -0
  69. /package/{public → dist}/icons/icon-128x128.png +0 -0
  70. /package/{public → dist}/icons/icon-128x128.svg +0 -0
  71. /package/{public → dist}/icons/icon-144x144.png +0 -0
  72. /package/{public → dist}/icons/icon-144x144.svg +0 -0
  73. /package/{public → dist}/icons/icon-152x152.png +0 -0
  74. /package/{public → dist}/icons/icon-152x152.svg +0 -0
  75. /package/{public → dist}/icons/icon-192x192.png +0 -0
  76. /package/{public → dist}/icons/icon-192x192.svg +0 -0
  77. /package/{public → dist}/icons/icon-384x384.png +0 -0
  78. /package/{public → dist}/icons/icon-384x384.svg +0 -0
  79. /package/{public → dist}/icons/icon-512x512.png +0 -0
  80. /package/{public → dist}/icons/icon-512x512.svg +0 -0
  81. /package/{public → dist}/icons/icon-72x72.png +0 -0
  82. /package/{public → dist}/icons/icon-72x72.svg +0 -0
  83. /package/{public → dist}/icons/icon-96x96.png +0 -0
  84. /package/{public → dist}/icons/icon-96x96.svg +0 -0
  85. /package/{public → dist}/icons/icon-template.svg +0 -0
  86. /package/{public → dist}/logo.svg +0 -0
  87. /package/{public → dist}/manifest.json +0 -0
  88. /package/{public → dist}/screenshots/cli-selection.png +0 -0
  89. /package/{public → dist}/screenshots/desktop-main.png +0 -0
  90. /package/{public → dist}/screenshots/mobile-chat.png +0 -0
  91. /package/{public → dist}/screenshots/tools-modal.png +0 -0
  92. /package/{public → dist}/sw.js +0 -0
@@ -1,324 +0,0 @@
1
- import React, { createContext, useContext, useEffect, useState, useCallback } from 'react';
2
- import { api } from '../utils/api';
3
- import { useAuth } from './AuthContext';
4
- import { useWebSocketContext } from './WebSocketContext';
5
-
6
- const TaskMasterContext = createContext({
7
- // TaskMaster project state
8
- projects: [],
9
- currentProject: null,
10
- projectTaskMaster: null,
11
-
12
- // MCP server state
13
- mcpServerStatus: null,
14
-
15
- // Tasks state
16
- tasks: [],
17
- nextTask: null,
18
-
19
- // Loading states
20
- isLoading: false,
21
- isLoadingTasks: false,
22
- isLoadingMCP: false,
23
-
24
- // Error state
25
- error: null,
26
-
27
- // Actions
28
- refreshProjects: () => {},
29
- setCurrentProject: () => {},
30
- refreshTasks: () => {},
31
- refreshMCPStatus: () => {},
32
- clearError: () => {}
33
- });
34
-
35
- export const useTaskMaster = () => {
36
- const context = useContext(TaskMasterContext);
37
- if (!context) {
38
- throw new Error('useTaskMaster must be used within a TaskMasterProvider');
39
- }
40
- return context;
41
- };
42
-
43
- export const TaskMasterProvider = ({ children }) => {
44
- // Get WebSocket messages from shared context to avoid duplicate connections
45
- const { messages } = useWebSocketContext();
46
-
47
- // Authentication context
48
- const { user, token, isLoading: authLoading } = useAuth();
49
-
50
- // State
51
- const [projects, setProjects] = useState([]);
52
- const [currentProject, setCurrentProjectState] = useState(null);
53
- const [projectTaskMaster, setProjectTaskMaster] = useState(null);
54
- const [mcpServerStatus, setMCPServerStatus] = useState(null);
55
- const [tasks, setTasks] = useState([]);
56
- const [nextTask, setNextTask] = useState(null);
57
- const [isLoading, setIsLoading] = useState(false);
58
- const [isLoadingTasks, setIsLoadingTasks] = useState(false);
59
- const [isLoadingMCP, setIsLoadingMCP] = useState(false);
60
- const [error, setError] = useState(null);
61
-
62
- // Helper to handle API errors
63
- const handleError = (error, context) => {
64
- console.error(`TaskMaster ${context} error:`, error);
65
- setError({
66
- message: error.message || `Failed to ${context}`,
67
- context,
68
- timestamp: new Date().toISOString()
69
- });
70
- };
71
-
72
- // Clear error state
73
- const clearError = useCallback(() => {
74
- setError(null);
75
- }, []);
76
-
77
- // This will be defined after the functions are declared
78
-
79
- // Refresh projects with TaskMaster metadata
80
- const refreshProjects = useCallback(async () => {
81
- // Only make API calls if user is authenticated
82
- if (!user || !token) {
83
- setProjects([]);
84
- setCurrentProjectState(null); // This might be the problem!
85
- return;
86
- }
87
-
88
- try {
89
- setIsLoading(true);
90
- clearError();
91
- const response = await api.get('/projects');
92
-
93
- if (!response.ok) {
94
- throw new Error(`Failed to fetch projects: ${response.status}`);
95
- }
96
-
97
- const projectsData = await response.json();
98
-
99
- // Check if projectsData is an array
100
- if (!Array.isArray(projectsData)) {
101
- console.error('Projects API returned non-array data:', projectsData);
102
- setProjects([]);
103
- return;
104
- }
105
-
106
- // Filter and enrich projects with TaskMaster data
107
- const enrichedProjects = projectsData.map(project => ({
108
- ...project,
109
- taskMasterConfigured: project.taskmaster?.hasTaskmaster || false,
110
- taskMasterStatus: project.taskmaster?.status || 'not-configured',
111
- taskCount: project.taskmaster?.metadata?.taskCount || 0,
112
- completedCount: project.taskmaster?.metadata?.completed || 0
113
- }));
114
-
115
- setProjects(enrichedProjects);
116
-
117
- // If current project is set, update its TaskMaster data
118
- if (currentProject) {
119
- const updatedCurrent = enrichedProjects.find(p => p.name === currentProject.name);
120
- if (updatedCurrent) {
121
- setCurrentProjectState(updatedCurrent);
122
- setProjectTaskMaster(updatedCurrent.taskmaster);
123
- }
124
- }
125
- } catch (err) {
126
- handleError(err, 'load projects');
127
- } finally {
128
- setIsLoading(false);
129
- }
130
- }, [user, token]); // Remove currentProject dependency to avoid infinite loops
131
-
132
- // Set current project and load its TaskMaster details
133
- const setCurrentProject = useCallback(async (project) => {
134
- try {
135
- setCurrentProjectState(project);
136
-
137
- // Clear previous project's data immediately when switching projects
138
- setTasks([]);
139
- setNextTask(null);
140
- setProjectTaskMaster(null); // Clear previous TaskMaster data
141
-
142
- // Try to fetch fresh TaskMaster detection data for the project
143
- if (project?.name) {
144
- try {
145
- const response = await api.get(`/taskmaster/detect/${encodeURIComponent(project.name)}`);
146
- if (response.ok) {
147
- const detectionData = await response.json();
148
- setProjectTaskMaster(detectionData.taskmaster);
149
- } else {
150
- // If individual detection fails, fall back to project data from /api/projects
151
- console.warn('Individual TaskMaster detection failed, using project data:', response.status);
152
- setProjectTaskMaster(project.taskmaster || null);
153
- }
154
- } catch (detectionError) {
155
- // If individual detection fails, fall back to project data from /api/projects
156
- console.warn('TaskMaster detection error, using project data:', detectionError.message);
157
- setProjectTaskMaster(project.taskmaster || null);
158
- }
159
- } else {
160
- setProjectTaskMaster(null);
161
- }
162
- } catch (err) {
163
- console.error('Error in setCurrentProject:', err);
164
- handleError(err, 'set current project');
165
- // Fall back to project data if available
166
- setProjectTaskMaster(project?.taskmaster || null);
167
- }
168
- }, []);
169
-
170
- // Refresh MCP server status
171
- const refreshMCPStatus = useCallback(async () => {
172
- // Only make API calls if user is authenticated
173
- if (!user || !token) {
174
- setMCPServerStatus(null);
175
- return;
176
- }
177
-
178
- try {
179
- setIsLoadingMCP(true);
180
- clearError();
181
- const mcpStatus = await api.get('/mcp-utils/taskmaster-server');
182
- setMCPServerStatus(mcpStatus);
183
- } catch (err) {
184
- handleError(err, 'check MCP server status');
185
- } finally {
186
- setIsLoadingMCP(false);
187
- }
188
- }, [user, token]);
189
-
190
- // Refresh tasks for current project - load real TaskMaster data
191
- const refreshTasks = useCallback(async () => {
192
- if (!currentProject) {
193
- setTasks([]);
194
- setNextTask(null);
195
- return;
196
- }
197
-
198
- // Only make API calls if user is authenticated
199
- if (!user || !token) {
200
- setTasks([]);
201
- setNextTask(null);
202
- return;
203
- }
204
-
205
- try {
206
- setIsLoadingTasks(true);
207
- clearError();
208
-
209
- // Load tasks from the TaskMaster API endpoint
210
- const response = await api.get(`/taskmaster/tasks/${encodeURIComponent(currentProject.name)}`);
211
-
212
- if (!response.ok) {
213
- const errorData = await response.json();
214
- throw new Error(errorData.message || 'Failed to load tasks');
215
- }
216
-
217
- const data = await response.json();
218
-
219
- setTasks(data.tasks || []);
220
-
221
- // Find next task (pending or in-progress)
222
- const nextTask = data.tasks?.find(task =>
223
- task.status === 'pending' || task.status === 'in-progress'
224
- ) || null;
225
- setNextTask(nextTask);
226
-
227
-
228
- } catch (err) {
229
- console.error('Error loading tasks:', err);
230
- handleError(err, 'load tasks');
231
- // Set empty state on error
232
- setTasks([]);
233
- setNextTask(null);
234
- } finally {
235
- setIsLoadingTasks(false);
236
- }
237
- }, [currentProject, user, token]);
238
-
239
- // Load initial data on mount or when auth changes
240
- useEffect(() => {
241
- if (!authLoading && user && token) {
242
- refreshProjects();
243
- refreshMCPStatus();
244
- } else {
245
- console.log('Auth not ready or no user, skipping project load:', { authLoading, user: !!user, token: !!token });
246
- }
247
- }, [refreshProjects, refreshMCPStatus, authLoading, user, token]);
248
-
249
- // Clear errors when authentication changes
250
- useEffect(() => {
251
- if (user && token) {
252
- clearError();
253
- }
254
- }, [user, token, clearError]);
255
-
256
- // Refresh tasks when current project changes
257
- useEffect(() => {
258
- if (currentProject?.name && user && token) {
259
- refreshTasks();
260
- }
261
- }, [currentProject?.name, user, token, refreshTasks]);
262
-
263
- // Handle WebSocket messages for TaskMaster updates
264
- useEffect(() => {
265
- const latestMessage = messages[messages.length - 1];
266
- if (!latestMessage) return;
267
-
268
-
269
- switch (latestMessage.type) {
270
- case 'taskmaster-project-updated':
271
- // Refresh projects when TaskMaster state changes
272
- if (latestMessage.projectName) {
273
- refreshProjects();
274
- }
275
- break;
276
-
277
- case 'taskmaster-tasks-updated':
278
- // Refresh tasks for the current project
279
- if (latestMessage.projectName === currentProject?.name) {
280
- refreshTasks();
281
- }
282
- break;
283
-
284
- case 'taskmaster-mcp-status-changed':
285
- // Refresh MCP server status
286
- refreshMCPStatus();
287
- break;
288
-
289
- default:
290
- // Ignore non-TaskMaster messages
291
- break;
292
- }
293
- }, [messages, refreshProjects, refreshTasks, refreshMCPStatus, currentProject]);
294
-
295
- // Context value
296
- const contextValue = {
297
- // State
298
- projects,
299
- currentProject,
300
- projectTaskMaster,
301
- mcpServerStatus,
302
- tasks,
303
- nextTask,
304
- isLoading,
305
- isLoadingTasks,
306
- isLoadingMCP,
307
- error,
308
-
309
- // Actions
310
- refreshProjects,
311
- setCurrentProject,
312
- refreshTasks,
313
- refreshMCPStatus,
314
- clearError
315
- };
316
-
317
- return (
318
- <TaskMasterContext.Provider value={contextValue}>
319
- {children}
320
- </TaskMasterContext.Provider>
321
- );
322
- };
323
-
324
- export default TaskMasterContext;
@@ -1,95 +0,0 @@
1
- import React, { createContext, useContext, useState, useEffect } from 'react';
2
- import { api } from '../utils/api';
3
-
4
- const TasksSettingsContext = createContext({
5
- tasksEnabled: true,
6
- setTasksEnabled: () => {},
7
- toggleTasksEnabled: () => {},
8
- isTaskMasterInstalled: null,
9
- isTaskMasterReady: null,
10
- installationStatus: null,
11
- isCheckingInstallation: true
12
- });
13
-
14
- export const useTasksSettings = () => {
15
- const context = useContext(TasksSettingsContext);
16
- if (!context) {
17
- throw new Error('useTasksSettings must be used within a TasksSettingsProvider');
18
- }
19
- return context;
20
- };
21
-
22
- export const TasksSettingsProvider = ({ children }) => {
23
- const [tasksEnabled, setTasksEnabled] = useState(() => {
24
- // Load from localStorage on initialization
25
- const saved = localStorage.getItem('tasks-enabled');
26
- return saved !== null ? JSON.parse(saved) : true; // Default to true
27
- });
28
-
29
- const [isTaskMasterInstalled, setIsTaskMasterInstalled] = useState(null);
30
- const [isTaskMasterReady, setIsTaskMasterReady] = useState(null);
31
- const [installationStatus, setInstallationStatus] = useState(null);
32
- const [isCheckingInstallation, setIsCheckingInstallation] = useState(true);
33
-
34
- // Save to localStorage whenever tasksEnabled changes
35
- useEffect(() => {
36
- localStorage.setItem('tasks-enabled', JSON.stringify(tasksEnabled));
37
- }, [tasksEnabled]);
38
-
39
- // Check TaskMaster installation status asynchronously on component mount
40
- useEffect(() => {
41
- const checkInstallation = async () => {
42
- try {
43
- const response = await api.get('/taskmaster/installation-status');
44
- if (response.ok) {
45
- const data = await response.json();
46
- setInstallationStatus(data);
47
- setIsTaskMasterInstalled(data.installation?.isInstalled || false);
48
- setIsTaskMasterReady(data.isReady || false);
49
-
50
- // If TaskMaster is not installed and user hasn't explicitly enabled tasks,
51
- // disable tasks automatically
52
- const userEnabledTasks = localStorage.getItem('tasks-enabled');
53
- if (!data.installation?.isInstalled && !userEnabledTasks) {
54
- setTasksEnabled(false);
55
- }
56
- } else {
57
- console.error('Failed to check TaskMaster installation status');
58
- setIsTaskMasterInstalled(false);
59
- setIsTaskMasterReady(false);
60
- }
61
- } catch (error) {
62
- console.error('Error checking TaskMaster installation:', error);
63
- setIsTaskMasterInstalled(false);
64
- setIsTaskMasterReady(false);
65
- } finally {
66
- setIsCheckingInstallation(false);
67
- }
68
- };
69
-
70
- // Run check asynchronously without blocking initial render
71
- setTimeout(checkInstallation, 0);
72
- }, []);
73
-
74
- const toggleTasksEnabled = () => {
75
- setTasksEnabled(prev => !prev);
76
- };
77
-
78
- const contextValue = {
79
- tasksEnabled,
80
- setTasksEnabled,
81
- toggleTasksEnabled,
82
- isTaskMasterInstalled,
83
- isTaskMasterReady,
84
- installationStatus,
85
- isCheckingInstallation
86
- };
87
-
88
- return (
89
- <TasksSettingsContext.Provider value={contextValue}>
90
- {children}
91
- </TasksSettingsContext.Provider>
92
- );
93
- };
94
-
95
- export default TasksSettingsContext;
@@ -1,94 +0,0 @@
1
- import React, { createContext, useContext, useState, useEffect } from 'react';
2
-
3
- const ThemeContext = createContext();
4
-
5
- export const useTheme = () => {
6
- const context = useContext(ThemeContext);
7
- if (!context) {
8
- throw new Error('useTheme must be used within a ThemeProvider');
9
- }
10
- return context;
11
- };
12
-
13
- export const ThemeProvider = ({ children }) => {
14
- // Check for saved theme preference or default to system preference
15
- const [isDarkMode, setIsDarkMode] = useState(() => {
16
- // Check localStorage first
17
- const savedTheme = localStorage.getItem('theme');
18
- if (savedTheme) {
19
- return savedTheme === 'dark';
20
- }
21
-
22
- // Check system preference
23
- if (window.matchMedia) {
24
- return window.matchMedia('(prefers-color-scheme: dark)').matches;
25
- }
26
-
27
- return false;
28
- });
29
-
30
- // Update document class and localStorage when theme changes
31
- useEffect(() => {
32
- if (isDarkMode) {
33
- document.documentElement.classList.add('dark');
34
- localStorage.setItem('theme', 'dark');
35
-
36
- // Update iOS status bar style and theme color for dark mode
37
- const statusBarMeta = document.querySelector('meta[name="apple-mobile-web-app-status-bar-style"]');
38
- if (statusBarMeta) {
39
- statusBarMeta.setAttribute('content', 'black-translucent');
40
- }
41
-
42
- const themeColorMeta = document.querySelector('meta[name="theme-color"]');
43
- if (themeColorMeta) {
44
- themeColorMeta.setAttribute('content', '#0c1117'); // Dark background color (hsl(222.2 84% 4.9%))
45
- }
46
- } else {
47
- document.documentElement.classList.remove('dark');
48
- localStorage.setItem('theme', 'light');
49
-
50
- // Update iOS status bar style and theme color for light mode
51
- const statusBarMeta = document.querySelector('meta[name="apple-mobile-web-app-status-bar-style"]');
52
- if (statusBarMeta) {
53
- statusBarMeta.setAttribute('content', 'default');
54
- }
55
-
56
- const themeColorMeta = document.querySelector('meta[name="theme-color"]');
57
- if (themeColorMeta) {
58
- themeColorMeta.setAttribute('content', '#ffffff'); // Light background color
59
- }
60
- }
61
- }, [isDarkMode]);
62
-
63
- // Listen for system theme changes
64
- useEffect(() => {
65
- if (!window.matchMedia) return;
66
-
67
- const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
68
- const handleChange = (e) => {
69
- // Only update if user hasn't manually set a preference
70
- const savedTheme = localStorage.getItem('theme');
71
- if (!savedTheme) {
72
- setIsDarkMode(e.matches);
73
- }
74
- };
75
-
76
- mediaQuery.addEventListener('change', handleChange);
77
- return () => mediaQuery.removeEventListener('change', handleChange);
78
- }, []);
79
-
80
- const toggleDarkMode = () => {
81
- setIsDarkMode(prev => !prev);
82
- };
83
-
84
- const value = {
85
- isDarkMode,
86
- toggleDarkMode,
87
- };
88
-
89
- return (
90
- <ThemeContext.Provider value={value}>
91
- {children}
92
- </ThemeContext.Provider>
93
- );
94
- };
@@ -1,29 +0,0 @@
1
- import React, { createContext, useContext } from 'react';
2
- import { useWebSocket } from '../utils/websocket';
3
-
4
- const WebSocketContext = createContext({
5
- ws: null,
6
- sendMessage: () => {},
7
- messages: [],
8
- isConnected: false
9
- });
10
-
11
- export const useWebSocketContext = () => {
12
- const context = useContext(WebSocketContext);
13
- if (!context) {
14
- throw new Error('useWebSocketContext must be used within a WebSocketProvider');
15
- }
16
- return context;
17
- };
18
-
19
- export const WebSocketProvider = ({ children }) => {
20
- const webSocketData = useWebSocket();
21
-
22
- return (
23
- <WebSocketContext.Provider value={webSocketData}>
24
- {children}
25
- </WebSocketContext.Provider>
26
- );
27
- };
28
-
29
- export default WebSocketContext;
@@ -1,109 +0,0 @@
1
- import { useState, useRef, useCallback } from 'react';
2
-
3
- export function useAudioRecorder() {
4
- const [isRecording, setRecording] = useState(false);
5
- const [audioBlob, setAudioBlob] = useState(null);
6
- const [error, setError] = useState(null);
7
- const mediaRecorderRef = useRef(null);
8
- const streamRef = useRef(null);
9
- const chunksRef = useRef([]);
10
-
11
- const start = useCallback(async () => {
12
- try {
13
- setError(null);
14
- setAudioBlob(null);
15
- chunksRef.current = [];
16
-
17
- // Request microphone access
18
- const stream = await navigator.mediaDevices.getUserMedia({
19
- audio: {
20
- echoCancellation: true,
21
- noiseSuppression: true,
22
- sampleRate: 16000,
23
- }
24
- });
25
-
26
- streamRef.current = stream;
27
-
28
- // Determine supported MIME type
29
- const mimeType = MediaRecorder.isTypeSupported('audio/webm')
30
- ? 'audio/webm'
31
- : 'audio/mp4';
32
-
33
- // Create media recorder
34
- const recorder = new MediaRecorder(stream, { mimeType });
35
- mediaRecorderRef.current = recorder;
36
-
37
- // Set up event handlers
38
- recorder.ondataavailable = (e) => {
39
- if (e.data.size > 0) {
40
- chunksRef.current.push(e.data);
41
- }
42
- };
43
-
44
- recorder.onstop = () => {
45
- // Create blob from chunks
46
- const blob = new Blob(chunksRef.current, { type: mimeType });
47
- setAudioBlob(blob);
48
-
49
- // Clean up stream
50
- if (streamRef.current) {
51
- streamRef.current.getTracks().forEach(track => track.stop());
52
- streamRef.current = null;
53
- }
54
- };
55
-
56
- recorder.onerror = (event) => {
57
- console.error('MediaRecorder error:', event);
58
- setError('Recording failed');
59
- setRecording(false);
60
- };
61
-
62
- // Start recording
63
- recorder.start();
64
- setRecording(true);
65
- console.log('Recording started');
66
- } catch (err) {
67
- console.error('Failed to start recording:', err);
68
- setError(err.message || 'Failed to start recording');
69
- setRecording(false);
70
- }
71
- }, []);
72
-
73
- const stop = useCallback(() => {
74
- console.log('Stop called, recorder state:', mediaRecorderRef.current?.state);
75
-
76
- try {
77
- if (mediaRecorderRef.current && mediaRecorderRef.current.state === 'recording') {
78
- mediaRecorderRef.current.stop();
79
- console.log('Recording stopped');
80
- }
81
- } catch (err) {
82
- console.error('Error stopping recorder:', err);
83
- }
84
-
85
- // Always update state
86
- setRecording(false);
87
-
88
- // Clean up stream if still active
89
- if (streamRef.current) {
90
- streamRef.current.getTracks().forEach(track => track.stop());
91
- streamRef.current = null;
92
- }
93
- }, []);
94
-
95
- const reset = useCallback(() => {
96
- setAudioBlob(null);
97
- setError(null);
98
- chunksRef.current = [];
99
- }, []);
100
-
101
- return {
102
- isRecording,
103
- audioBlob,
104
- error,
105
- start,
106
- stop,
107
- reset
108
- };
109
- }
@@ -1,39 +0,0 @@
1
- // hooks/useVersionCheck.js
2
- import { useState, useEffect } from 'react';
3
- import { version } from '../../package.json';
4
-
5
- export const useVersionCheck = (owner, repo) => {
6
- const [updateAvailable, setUpdateAvailable] = useState(false);
7
- const [latestVersion, setLatestVersion] = useState(null);
8
-
9
- useEffect(() => {
10
- const checkVersion = async () => {
11
- try {
12
- const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/releases/latest`);
13
- const data = await response.json();
14
-
15
- // Handle the case where there might not be any releases
16
- if (data.tag_name) {
17
- const latest = data.tag_name.replace(/^v/, '');
18
- setLatestVersion(latest);
19
- setUpdateAvailable(version !== latest);
20
- } else {
21
- // No releases found, don't show update notification
22
- setUpdateAvailable(false);
23
- setLatestVersion(null);
24
- }
25
- } catch (error) {
26
- console.error('Version check failed:', error);
27
- // On error, don't show update notification
28
- setUpdateAvailable(false);
29
- setLatestVersion(null);
30
- }
31
- };
32
-
33
- checkVersion();
34
- const interval = setInterval(checkVersion, 5 * 60 * 1000); // Check every 5 minutes
35
- return () => clearInterval(interval);
36
- }, [owner, repo]);
37
-
38
- return { updateAvailable, latestVersion, currentVersion: version };
39
- };