@siteboon/claude-code-ui 1.8.2
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/.env.example +12 -0
- package/.nvmrc +1 -0
- package/LICENSE +675 -0
- package/README.md +275 -0
- package/index.html +48 -0
- package/package.json +84 -0
- package/postcss.config.js +6 -0
- package/public/convert-icons.md +53 -0
- package/public/favicon.png +0 -0
- package/public/favicon.svg +9 -0
- package/public/generate-icons.js +49 -0
- package/public/icons/claude-ai-icon.svg +1 -0
- package/public/icons/cursor.svg +1 -0
- package/public/icons/generate-icons.md +19 -0
- package/public/icons/icon-128x128.png +0 -0
- package/public/icons/icon-128x128.svg +12 -0
- package/public/icons/icon-144x144.png +0 -0
- package/public/icons/icon-144x144.svg +12 -0
- package/public/icons/icon-152x152.png +0 -0
- package/public/icons/icon-152x152.svg +12 -0
- package/public/icons/icon-192x192.png +0 -0
- package/public/icons/icon-192x192.svg +12 -0
- package/public/icons/icon-384x384.png +0 -0
- package/public/icons/icon-384x384.svg +12 -0
- package/public/icons/icon-512x512.png +0 -0
- package/public/icons/icon-512x512.svg +12 -0
- package/public/icons/icon-72x72.png +0 -0
- package/public/icons/icon-72x72.svg +12 -0
- package/public/icons/icon-96x96.png +0 -0
- package/public/icons/icon-96x96.svg +12 -0
- package/public/icons/icon-template.svg +12 -0
- package/public/logo.svg +9 -0
- package/public/manifest.json +61 -0
- package/public/screenshots/cli-selection.png +0 -0
- package/public/screenshots/desktop-main.png +0 -0
- package/public/screenshots/mobile-chat.png +0 -0
- package/public/screenshots/tools-modal.png +0 -0
- package/public/sw.js +49 -0
- package/server/claude-cli.js +391 -0
- package/server/cursor-cli.js +250 -0
- package/server/database/db.js +86 -0
- package/server/database/init.sql +16 -0
- package/server/index.js +1167 -0
- package/server/middleware/auth.js +80 -0
- package/server/projects.js +1063 -0
- package/server/routes/auth.js +135 -0
- package/server/routes/cursor.js +794 -0
- package/server/routes/git.js +823 -0
- package/server/routes/mcp-utils.js +48 -0
- package/server/routes/mcp.js +552 -0
- package/server/routes/taskmaster.js +1971 -0
- package/server/utils/mcp-detector.js +198 -0
- package/server/utils/taskmaster-websocket.js +129 -0
- package/src/App.jsx +751 -0
- package/src/components/ChatInterface.jsx +3485 -0
- package/src/components/ClaudeLogo.jsx +11 -0
- package/src/components/ClaudeStatus.jsx +107 -0
- package/src/components/CodeEditor.jsx +422 -0
- package/src/components/CreateTaskModal.jsx +88 -0
- package/src/components/CursorLogo.jsx +9 -0
- package/src/components/DarkModeToggle.jsx +35 -0
- package/src/components/DiffViewer.jsx +41 -0
- package/src/components/ErrorBoundary.jsx +73 -0
- package/src/components/FileTree.jsx +480 -0
- package/src/components/GitPanel.jsx +1283 -0
- package/src/components/ImageViewer.jsx +54 -0
- package/src/components/LoginForm.jsx +110 -0
- package/src/components/MainContent.jsx +577 -0
- package/src/components/MicButton.jsx +272 -0
- package/src/components/MobileNav.jsx +88 -0
- package/src/components/NextTaskBanner.jsx +695 -0
- package/src/components/PRDEditor.jsx +871 -0
- package/src/components/ProtectedRoute.jsx +44 -0
- package/src/components/QuickSettingsPanel.jsx +262 -0
- package/src/components/Settings.jsx +2023 -0
- package/src/components/SetupForm.jsx +135 -0
- package/src/components/Shell.jsx +663 -0
- package/src/components/Sidebar.jsx +1665 -0
- package/src/components/StandaloneShell.jsx +106 -0
- package/src/components/TaskCard.jsx +210 -0
- package/src/components/TaskDetail.jsx +406 -0
- package/src/components/TaskIndicator.jsx +108 -0
- package/src/components/TaskList.jsx +1054 -0
- package/src/components/TaskMasterSetupWizard.jsx +603 -0
- package/src/components/TaskMasterStatus.jsx +86 -0
- package/src/components/TodoList.jsx +91 -0
- package/src/components/Tooltip.jsx +91 -0
- package/src/components/ui/badge.jsx +31 -0
- package/src/components/ui/button.jsx +46 -0
- package/src/components/ui/input.jsx +19 -0
- package/src/components/ui/scroll-area.jsx +23 -0
- package/src/contexts/AuthContext.jsx +158 -0
- package/src/contexts/TaskMasterContext.jsx +324 -0
- package/src/contexts/TasksSettingsContext.jsx +95 -0
- package/src/contexts/ThemeContext.jsx +94 -0
- package/src/contexts/WebSocketContext.jsx +29 -0
- package/src/hooks/useAudioRecorder.js +109 -0
- package/src/hooks/useVersionCheck.js +39 -0
- package/src/index.css +822 -0
- package/src/lib/utils.js +6 -0
- package/src/main.jsx +10 -0
- package/src/utils/api.js +141 -0
- package/src/utils/websocket.js +109 -0
- package/src/utils/whisper.js +37 -0
- package/tailwind.config.js +63 -0
- package/vite.config.js +29 -0
|
@@ -0,0 +1,695 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { ArrowRight, List, Clock, Flag, CheckCircle, Circle, AlertCircle, Pause, ChevronDown, ChevronUp, Plus, FileText, Settings, X, Terminal, Eye, Play, Zap, Target } from 'lucide-react';
|
|
3
|
+
import { cn } from '../lib/utils';
|
|
4
|
+
import { useTaskMaster } from '../contexts/TaskMasterContext';
|
|
5
|
+
import { api } from '../utils/api';
|
|
6
|
+
import Shell from './Shell';
|
|
7
|
+
import TaskDetail from './TaskDetail';
|
|
8
|
+
|
|
9
|
+
const NextTaskBanner = ({ onShowAllTasks, onStartTask, className = '' }) => {
|
|
10
|
+
const { nextTask, tasks, currentProject, isLoadingTasks, projectTaskMaster, refreshTasks, refreshProjects } = useTaskMaster();
|
|
11
|
+
const [showDetails, setShowDetails] = useState(false);
|
|
12
|
+
const [showTaskOptions, setShowTaskOptions] = useState(false);
|
|
13
|
+
const [showCreateTaskModal, setShowCreateTaskModal] = useState(false);
|
|
14
|
+
const [showTemplateSelector, setShowTemplateSelector] = useState(false);
|
|
15
|
+
const [showCLI, setShowCLI] = useState(false);
|
|
16
|
+
const [showTaskDetail, setShowTaskDetail] = useState(false);
|
|
17
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
18
|
+
|
|
19
|
+
// Handler functions
|
|
20
|
+
const handleInitializeTaskMaster = async () => {
|
|
21
|
+
if (!currentProject) return;
|
|
22
|
+
|
|
23
|
+
setIsLoading(true);
|
|
24
|
+
try {
|
|
25
|
+
const response = await api.taskmaster.init(currentProject.name);
|
|
26
|
+
if (response.ok) {
|
|
27
|
+
await refreshProjects();
|
|
28
|
+
setShowTaskOptions(false);
|
|
29
|
+
} else {
|
|
30
|
+
const error = await response.json();
|
|
31
|
+
console.error('Failed to initialize TaskMaster:', error);
|
|
32
|
+
alert(`Failed to initialize TaskMaster: ${error.message}`);
|
|
33
|
+
}
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error('Error initializing TaskMaster:', error);
|
|
36
|
+
alert('Error initializing TaskMaster. Please try again.');
|
|
37
|
+
} finally {
|
|
38
|
+
setIsLoading(false);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const handleCreateManualTask = () => {
|
|
43
|
+
setShowCreateTaskModal(true);
|
|
44
|
+
setShowTaskOptions(false);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const handleParsePRD = () => {
|
|
48
|
+
setShowTemplateSelector(true);
|
|
49
|
+
setShowTaskOptions(false);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Don't show if no project or still loading
|
|
53
|
+
if (!currentProject || isLoadingTasks) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let bannerContent;
|
|
58
|
+
|
|
59
|
+
// Show setup message only if no tasks exist AND TaskMaster is not configured
|
|
60
|
+
if ((!tasks || tasks.length === 0) && !projectTaskMaster?.hasTaskmaster) {
|
|
61
|
+
bannerContent = (
|
|
62
|
+
<div className={cn(
|
|
63
|
+
'bg-blue-50 dark:bg-blue-950 border border-blue-200 dark:border-blue-800 rounded-lg p-3 mb-4',
|
|
64
|
+
className
|
|
65
|
+
)}>
|
|
66
|
+
<div className="flex items-center justify-between">
|
|
67
|
+
<div className="flex items-center gap-2">
|
|
68
|
+
<List className="w-4 h-4 text-blue-600 dark:text-blue-400" />
|
|
69
|
+
<div>
|
|
70
|
+
<div className="text-sm font-medium text-gray-900 dark:text-white">
|
|
71
|
+
TaskMaster AI is not configured
|
|
72
|
+
</div>
|
|
73
|
+
<div className="text-xs text-gray-600 dark:text-gray-400 mt-0.5">
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
<div className="flex items-center gap-1">
|
|
78
|
+
<button
|
|
79
|
+
onClick={() => setShowTaskOptions(!showTaskOptions)}
|
|
80
|
+
className="text-xs px-2 py-1 bg-blue-600 hover:bg-blue-700 text-white rounded transition-colors flex items-center gap-1"
|
|
81
|
+
>
|
|
82
|
+
<Settings className="w-3 h-3" />
|
|
83
|
+
Initialize TaskMaster AI
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
{showTaskOptions && (
|
|
89
|
+
<div className="mt-3 pt-3 border-t border-blue-200 dark:border-blue-800">
|
|
90
|
+
{!projectTaskMaster?.hasTaskmaster && (
|
|
91
|
+
<div className="mb-3 p-3 bg-blue-50 dark:bg-blue-900/50 rounded-lg">
|
|
92
|
+
<h4 className="text-sm font-medium text-blue-900 dark:text-blue-100 mb-2">
|
|
93
|
+
🎯 What is TaskMaster?
|
|
94
|
+
</h4>
|
|
95
|
+
<div className="text-xs text-blue-800 dark:text-blue-200 space-y-1">
|
|
96
|
+
<p>• <strong>AI-Powered Task Management:</strong> Break complex projects into manageable subtasks</p>
|
|
97
|
+
<p>• <strong>PRD Templates:</strong> Generate tasks from Product Requirements Documents</p>
|
|
98
|
+
<p>• <strong>Dependency Tracking:</strong> Understand task relationships and execution order</p>
|
|
99
|
+
<p>• <strong>Progress Visualization:</strong> Kanban boards and detailed task analytics</p>
|
|
100
|
+
<p>• <strong>CLI Integration:</strong> Use taskmaster commands for advanced workflows</p>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
)}
|
|
104
|
+
<div className="flex flex-col gap-2">
|
|
105
|
+
{!projectTaskMaster?.hasTaskmaster ? (
|
|
106
|
+
<button
|
|
107
|
+
className="text-xs px-3 py-2 bg-slate-100 dark:bg-slate-800 hover:bg-slate-200 dark:hover:bg-slate-700 text-slate-800 dark:text-slate-200 rounded transition-colors text-left flex items-center gap-2"
|
|
108
|
+
onClick={() => setShowCLI(true)}
|
|
109
|
+
>
|
|
110
|
+
<Terminal className="w-3 h-3" />
|
|
111
|
+
Initialize TaskMaster
|
|
112
|
+
</button>
|
|
113
|
+
) : (
|
|
114
|
+
<>
|
|
115
|
+
<div className="mb-2 p-2 bg-green-50 dark:bg-green-900/30 rounded text-xs text-green-800 dark:text-green-200">
|
|
116
|
+
<strong>Add more tasks:</strong> Create additional tasks manually or generate them from a PRD template
|
|
117
|
+
</div>
|
|
118
|
+
<button
|
|
119
|
+
className="text-xs px-3 py-2 bg-green-100 dark:bg-green-900 hover:bg-green-200 dark:hover:bg-green-800 text-green-800 dark:text-green-200 rounded transition-colors text-left flex items-center gap-2 disabled:opacity-50"
|
|
120
|
+
onClick={handleCreateManualTask}
|
|
121
|
+
disabled={isLoading}
|
|
122
|
+
>
|
|
123
|
+
<Plus className="w-3 h-3" />
|
|
124
|
+
Create a new task manually
|
|
125
|
+
</button>
|
|
126
|
+
<button
|
|
127
|
+
className="text-xs px-3 py-2 bg-purple-100 dark:bg-purple-900 hover:bg-purple-200 dark:hover:bg-purple-800 text-purple-800 dark:text-purple-200 rounded transition-colors text-left flex items-center gap-2 disabled:opacity-50"
|
|
128
|
+
onClick={handleParsePRD}
|
|
129
|
+
disabled={isLoading}
|
|
130
|
+
>
|
|
131
|
+
<FileText className="w-3 h-3" />
|
|
132
|
+
{isLoading ? 'Parsing...' : 'Generate tasks from PRD template'}
|
|
133
|
+
</button>
|
|
134
|
+
</>
|
|
135
|
+
)}
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
)}
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
} else if (nextTask) {
|
|
142
|
+
// Show next task if available
|
|
143
|
+
bannerContent = (
|
|
144
|
+
<div className={cn(
|
|
145
|
+
'bg-slate-50 dark:bg-slate-900/30 border border-slate-200 dark:border-slate-700 rounded-lg p-3 mb-4',
|
|
146
|
+
className
|
|
147
|
+
)}>
|
|
148
|
+
<div className="flex items-center justify-between gap-3">
|
|
149
|
+
<div className="flex-1 min-w-0">
|
|
150
|
+
<div className="flex items-center gap-2 mb-1">
|
|
151
|
+
<div className="w-5 h-5 bg-blue-100 dark:bg-blue-900/50 rounded-full flex items-center justify-center flex-shrink-0">
|
|
152
|
+
<Target className="w-3 h-3 text-blue-600 dark:text-blue-400" />
|
|
153
|
+
</div>
|
|
154
|
+
<span className="text-xs text-slate-600 dark:text-slate-400 font-medium">Task {nextTask.id}</span>
|
|
155
|
+
{nextTask.priority === 'high' && (
|
|
156
|
+
<div className="w-4 h-4 rounded bg-red-100 dark:bg-red-900/50 flex items-center justify-center" title="High Priority">
|
|
157
|
+
<Zap className="w-2.5 h-2.5 text-red-600 dark:text-red-400" />
|
|
158
|
+
</div>
|
|
159
|
+
)}
|
|
160
|
+
{nextTask.priority === 'medium' && (
|
|
161
|
+
<div className="w-4 h-4 rounded bg-amber-100 dark:bg-amber-900/50 flex items-center justify-center" title="Medium Priority">
|
|
162
|
+
<Flag className="w-2.5 h-2.5 text-amber-600 dark:text-amber-400" />
|
|
163
|
+
</div>
|
|
164
|
+
)}
|
|
165
|
+
{nextTask.priority === 'low' && (
|
|
166
|
+
<div className="w-4 h-4 rounded bg-gray-100 dark:bg-gray-800 flex items-center justify-center" title="Low Priority">
|
|
167
|
+
<Circle className="w-2.5 h-2.5 text-gray-400 dark:text-gray-500" />
|
|
168
|
+
</div>
|
|
169
|
+
)}
|
|
170
|
+
</div>
|
|
171
|
+
<p className="text-sm font-medium text-slate-900 dark:text-slate-100 line-clamp-1">
|
|
172
|
+
{nextTask.title}
|
|
173
|
+
</p>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<div className="flex items-center gap-1 flex-shrink-0">
|
|
177
|
+
<button
|
|
178
|
+
onClick={() => onStartTask?.()}
|
|
179
|
+
className="text-xs px-3 py-1.5 bg-blue-600 hover:bg-blue-700 text-white rounded-md font-medium transition-colors shadow-sm flex items-center gap-1"
|
|
180
|
+
>
|
|
181
|
+
<Play className="w-3 h-3" />
|
|
182
|
+
Start Task
|
|
183
|
+
</button>
|
|
184
|
+
<button
|
|
185
|
+
onClick={() => setShowTaskDetail(true)}
|
|
186
|
+
className="text-xs px-2 py-1.5 border border-slate-300 dark:border-slate-600 hover:bg-slate-100 dark:hover:bg-slate-800 text-slate-600 dark:text-slate-300 rounded-md transition-colors flex items-center gap-1"
|
|
187
|
+
title="View task details"
|
|
188
|
+
>
|
|
189
|
+
<Eye className="w-3 h-3" />
|
|
190
|
+
</button>
|
|
191
|
+
{onShowAllTasks && (
|
|
192
|
+
<button
|
|
193
|
+
onClick={onShowAllTasks}
|
|
194
|
+
className="text-xs px-2 py-1.5 border border-slate-300 dark:border-slate-600 hover:bg-slate-100 dark:hover:bg-slate-800 text-slate-600 dark:text-slate-300 rounded-md transition-colors flex items-center gap-1"
|
|
195
|
+
title="View all tasks"
|
|
196
|
+
>
|
|
197
|
+
<List className="w-3 h-3" />
|
|
198
|
+
</button>
|
|
199
|
+
)}
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
|
|
203
|
+
</div>
|
|
204
|
+
);
|
|
205
|
+
} else if (tasks && tasks.length > 0) {
|
|
206
|
+
// Show completion message only if there are tasks and all are done
|
|
207
|
+
const completedTasks = tasks.filter(task => task.status === 'done').length;
|
|
208
|
+
const totalTasks = tasks.length;
|
|
209
|
+
|
|
210
|
+
bannerContent = (
|
|
211
|
+
<div className={cn(
|
|
212
|
+
'bg-purple-50 dark:bg-purple-950 border border-purple-200 dark:border-purple-800 rounded-lg p-3 mb-4',
|
|
213
|
+
className
|
|
214
|
+
)}>
|
|
215
|
+
<div className="flex items-center justify-between">
|
|
216
|
+
<div className="flex items-center gap-2">
|
|
217
|
+
<CheckCircle className="w-4 h-4 text-purple-600 dark:text-purple-400" />
|
|
218
|
+
<span className="text-sm font-medium text-gray-900 dark:text-white">
|
|
219
|
+
{completedTasks === totalTasks ? "All done! 🎉" : "No pending tasks"}
|
|
220
|
+
</span>
|
|
221
|
+
</div>
|
|
222
|
+
<div className="flex items-center gap-2">
|
|
223
|
+
<span className="text-xs text-gray-600 dark:text-gray-400">
|
|
224
|
+
{completedTasks}/{totalTasks}
|
|
225
|
+
</span>
|
|
226
|
+
<button
|
|
227
|
+
onClick={onShowAllTasks}
|
|
228
|
+
className="text-xs px-2 py-1 bg-purple-600 hover:bg-purple-700 text-white rounded transition-colors"
|
|
229
|
+
>
|
|
230
|
+
Review
|
|
231
|
+
</button>
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
);
|
|
236
|
+
} else {
|
|
237
|
+
// TaskMaster is configured but no tasks exist - don't show anything in chat
|
|
238
|
+
bannerContent = null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<>
|
|
243
|
+
{bannerContent}
|
|
244
|
+
|
|
245
|
+
{/* Create Task Modal */}
|
|
246
|
+
{showCreateTaskModal && (
|
|
247
|
+
<CreateTaskModal
|
|
248
|
+
currentProject={currentProject}
|
|
249
|
+
onClose={() => setShowCreateTaskModal(false)}
|
|
250
|
+
onTaskCreated={() => {
|
|
251
|
+
refreshTasks();
|
|
252
|
+
setShowCreateTaskModal(false);
|
|
253
|
+
}}
|
|
254
|
+
/>
|
|
255
|
+
)}
|
|
256
|
+
|
|
257
|
+
{/* Template Selector Modal */}
|
|
258
|
+
{showTemplateSelector && (
|
|
259
|
+
<TemplateSelector
|
|
260
|
+
currentProject={currentProject}
|
|
261
|
+
onClose={() => setShowTemplateSelector(false)}
|
|
262
|
+
onTemplateApplied={() => {
|
|
263
|
+
refreshTasks();
|
|
264
|
+
setShowTemplateSelector(false);
|
|
265
|
+
}}
|
|
266
|
+
/>
|
|
267
|
+
)}
|
|
268
|
+
|
|
269
|
+
{/* TaskMaster CLI Setup Modal */}
|
|
270
|
+
{showCLI && (
|
|
271
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm">
|
|
272
|
+
<div className="bg-white dark:bg-gray-900 rounded-lg shadow-xl border border-gray-200 dark:border-gray-700 w-full max-w-4xl h-[600px] flex flex-col">
|
|
273
|
+
{/* Modal Header */}
|
|
274
|
+
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
|
|
275
|
+
<div className="flex items-center gap-3">
|
|
276
|
+
<div className="w-8 h-8 bg-blue-100 dark:bg-blue-900/50 rounded-lg flex items-center justify-center">
|
|
277
|
+
<Terminal className="w-4 h-4 text-blue-600 dark:text-blue-400" />
|
|
278
|
+
</div>
|
|
279
|
+
<div>
|
|
280
|
+
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">TaskMaster Setup</h2>
|
|
281
|
+
<p className="text-sm text-gray-500 dark:text-gray-400">Interactive CLI for {currentProject?.displayName}</p>
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
<button
|
|
285
|
+
onClick={() => setShowCLI(false)}
|
|
286
|
+
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-800"
|
|
287
|
+
>
|
|
288
|
+
<X className="w-5 h-5" />
|
|
289
|
+
</button>
|
|
290
|
+
</div>
|
|
291
|
+
|
|
292
|
+
{/* Terminal Container */}
|
|
293
|
+
<div className="flex-1 p-4">
|
|
294
|
+
<div className="h-full bg-black rounded-lg overflow-hidden">
|
|
295
|
+
<Shell
|
|
296
|
+
selectedProject={currentProject}
|
|
297
|
+
selectedSession={null}
|
|
298
|
+
isActive={true}
|
|
299
|
+
initialCommand="npx task-master init"
|
|
300
|
+
isPlainShell={true}
|
|
301
|
+
/>
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
|
|
305
|
+
{/* Modal Footer */}
|
|
306
|
+
<div className="p-4 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50">
|
|
307
|
+
<div className="flex items-center justify-between">
|
|
308
|
+
<div className="text-sm text-gray-600 dark:text-gray-400">
|
|
309
|
+
TaskMaster initialization will start automatically
|
|
310
|
+
</div>
|
|
311
|
+
<button
|
|
312
|
+
onClick={() => setShowCLI(false)}
|
|
313
|
+
className="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-md hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors"
|
|
314
|
+
>
|
|
315
|
+
Close
|
|
316
|
+
</button>
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
</div>
|
|
321
|
+
)}
|
|
322
|
+
|
|
323
|
+
{/* Task Detail Modal */}
|
|
324
|
+
{showTaskDetail && nextTask && (
|
|
325
|
+
<TaskDetail
|
|
326
|
+
task={nextTask}
|
|
327
|
+
isOpen={showTaskDetail}
|
|
328
|
+
onClose={() => setShowTaskDetail(false)}
|
|
329
|
+
onStatusChange={() => refreshTasks?.()}
|
|
330
|
+
onTaskClick={null} // Disable dependency navigation in NextTaskBanner for now
|
|
331
|
+
/>
|
|
332
|
+
)}
|
|
333
|
+
</>
|
|
334
|
+
);
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
// Simple Create Task Modal Component
|
|
338
|
+
const CreateTaskModal = ({ currentProject, onClose, onTaskCreated }) => {
|
|
339
|
+
const [formData, setFormData] = useState({
|
|
340
|
+
title: '',
|
|
341
|
+
description: '',
|
|
342
|
+
priority: 'medium',
|
|
343
|
+
useAI: false,
|
|
344
|
+
prompt: ''
|
|
345
|
+
});
|
|
346
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
347
|
+
|
|
348
|
+
const handleSubmit = async (e) => {
|
|
349
|
+
e.preventDefault();
|
|
350
|
+
if (!currentProject) return;
|
|
351
|
+
|
|
352
|
+
setIsSubmitting(true);
|
|
353
|
+
try {
|
|
354
|
+
const taskData = formData.useAI
|
|
355
|
+
? { prompt: formData.prompt, priority: formData.priority }
|
|
356
|
+
: { title: formData.title, description: formData.description, priority: formData.priority };
|
|
357
|
+
|
|
358
|
+
const response = await api.taskmaster.addTask(currentProject.name, taskData);
|
|
359
|
+
|
|
360
|
+
if (response.ok) {
|
|
361
|
+
onTaskCreated();
|
|
362
|
+
} else {
|
|
363
|
+
const error = await response.json();
|
|
364
|
+
console.error('Failed to create task:', error);
|
|
365
|
+
alert(`Failed to create task: ${error.message}`);
|
|
366
|
+
}
|
|
367
|
+
} catch (error) {
|
|
368
|
+
console.error('Error creating task:', error);
|
|
369
|
+
alert('Error creating task. Please try again.');
|
|
370
|
+
} finally {
|
|
371
|
+
setIsSubmitting(false);
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
return (
|
|
376
|
+
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
|
377
|
+
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 w-full max-w-md">
|
|
378
|
+
<div className="flex items-center justify-between mb-4">
|
|
379
|
+
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">Create New Task</h3>
|
|
380
|
+
<button
|
|
381
|
+
onClick={onClose}
|
|
382
|
+
className="p-1 hover:bg-gray-200 dark:hover:bg-gray-700 rounded"
|
|
383
|
+
>
|
|
384
|
+
<X className="w-4 h-4" />
|
|
385
|
+
</button>
|
|
386
|
+
</div>
|
|
387
|
+
|
|
388
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
389
|
+
<div>
|
|
390
|
+
<label className="flex items-center gap-2 text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
391
|
+
<input
|
|
392
|
+
type="checkbox"
|
|
393
|
+
checked={formData.useAI}
|
|
394
|
+
onChange={(e) => setFormData(prev => ({ ...prev, useAI: e.target.checked }))}
|
|
395
|
+
/>
|
|
396
|
+
Use AI to generate task details
|
|
397
|
+
</label>
|
|
398
|
+
</div>
|
|
399
|
+
|
|
400
|
+
{formData.useAI ? (
|
|
401
|
+
<div>
|
|
402
|
+
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
403
|
+
Task Description (AI will generate details)
|
|
404
|
+
</label>
|
|
405
|
+
<textarea
|
|
406
|
+
value={formData.prompt}
|
|
407
|
+
onChange={(e) => setFormData(prev => ({ ...prev, prompt: e.target.value }))}
|
|
408
|
+
className="w-full p-2 border border-gray-300 dark:border-gray-600 rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
|
409
|
+
rows="3"
|
|
410
|
+
placeholder="Describe what you want to accomplish..."
|
|
411
|
+
required
|
|
412
|
+
/>
|
|
413
|
+
</div>
|
|
414
|
+
) : (
|
|
415
|
+
<>
|
|
416
|
+
<div>
|
|
417
|
+
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
418
|
+
Task Title
|
|
419
|
+
</label>
|
|
420
|
+
<input
|
|
421
|
+
type="text"
|
|
422
|
+
value={formData.title}
|
|
423
|
+
onChange={(e) => setFormData(prev => ({ ...prev, title: e.target.value }))}
|
|
424
|
+
className="w-full p-2 border border-gray-300 dark:border-gray-600 rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
|
425
|
+
placeholder="Enter task title..."
|
|
426
|
+
required
|
|
427
|
+
/>
|
|
428
|
+
</div>
|
|
429
|
+
|
|
430
|
+
<div>
|
|
431
|
+
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
432
|
+
Description
|
|
433
|
+
</label>
|
|
434
|
+
<textarea
|
|
435
|
+
value={formData.description}
|
|
436
|
+
onChange={(e) => setFormData(prev => ({ ...prev, description: e.target.value }))}
|
|
437
|
+
className="w-full p-2 border border-gray-300 dark:border-gray-600 rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
|
438
|
+
rows="3"
|
|
439
|
+
placeholder="Describe the task..."
|
|
440
|
+
required
|
|
441
|
+
/>
|
|
442
|
+
</div>
|
|
443
|
+
</>
|
|
444
|
+
)}
|
|
445
|
+
|
|
446
|
+
<div>
|
|
447
|
+
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
448
|
+
Priority
|
|
449
|
+
</label>
|
|
450
|
+
<select
|
|
451
|
+
value={formData.priority}
|
|
452
|
+
onChange={(e) => setFormData(prev => ({ ...prev, priority: e.target.value }))}
|
|
453
|
+
className="w-full p-2 border border-gray-300 dark:border-gray-600 rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
|
454
|
+
>
|
|
455
|
+
<option value="low">Low</option>
|
|
456
|
+
<option value="medium">Medium</option>
|
|
457
|
+
<option value="high">High</option>
|
|
458
|
+
</select>
|
|
459
|
+
</div>
|
|
460
|
+
|
|
461
|
+
<div className="flex gap-2 pt-4">
|
|
462
|
+
<button
|
|
463
|
+
type="button"
|
|
464
|
+
onClick={onClose}
|
|
465
|
+
className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300"
|
|
466
|
+
disabled={isSubmitting}
|
|
467
|
+
>
|
|
468
|
+
Cancel
|
|
469
|
+
</button>
|
|
470
|
+
<button
|
|
471
|
+
type="submit"
|
|
472
|
+
className="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded disabled:opacity-50"
|
|
473
|
+
disabled={isSubmitting || (formData.useAI && !formData.prompt.trim()) || (!formData.useAI && (!formData.title.trim() || !formData.description.trim()))}
|
|
474
|
+
>
|
|
475
|
+
{isSubmitting ? 'Creating...' : 'Create Task'}
|
|
476
|
+
</button>
|
|
477
|
+
</div>
|
|
478
|
+
</form>
|
|
479
|
+
</div>
|
|
480
|
+
</div>
|
|
481
|
+
);
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
// Template Selector Modal Component
|
|
485
|
+
const TemplateSelector = ({ currentProject, onClose, onTemplateApplied }) => {
|
|
486
|
+
const [templates, setTemplates] = useState([]);
|
|
487
|
+
const [selectedTemplate, setSelectedTemplate] = useState(null);
|
|
488
|
+
const [customizations, setCustomizations] = useState({});
|
|
489
|
+
const [fileName, setFileName] = useState('prd.txt');
|
|
490
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
491
|
+
const [isApplying, setIsApplying] = useState(false);
|
|
492
|
+
const [step, setStep] = useState('select'); // 'select', 'customize', 'generate'
|
|
493
|
+
|
|
494
|
+
useEffect(() => {
|
|
495
|
+
const loadTemplates = async () => {
|
|
496
|
+
try {
|
|
497
|
+
const response = await api.taskmaster.getTemplates();
|
|
498
|
+
if (response.ok) {
|
|
499
|
+
const data = await response.json();
|
|
500
|
+
setTemplates(data.templates);
|
|
501
|
+
}
|
|
502
|
+
} catch (error) {
|
|
503
|
+
console.error('Error loading templates:', error);
|
|
504
|
+
} finally {
|
|
505
|
+
setIsLoading(false);
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
loadTemplates();
|
|
510
|
+
}, []);
|
|
511
|
+
|
|
512
|
+
const handleSelectTemplate = (template) => {
|
|
513
|
+
setSelectedTemplate(template);
|
|
514
|
+
// Find placeholders in template content
|
|
515
|
+
const placeholders = template.content.match(/\[([^\]]+)\]/g) || [];
|
|
516
|
+
const uniquePlaceholders = [...new Set(placeholders.map(p => p.slice(1, -1)))];
|
|
517
|
+
|
|
518
|
+
const initialCustomizations = {};
|
|
519
|
+
uniquePlaceholders.forEach(placeholder => {
|
|
520
|
+
initialCustomizations[placeholder] = '';
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
setCustomizations(initialCustomizations);
|
|
524
|
+
setStep('customize');
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
const handleApplyTemplate = async () => {
|
|
528
|
+
if (!selectedTemplate || !currentProject) return;
|
|
529
|
+
|
|
530
|
+
setIsApplying(true);
|
|
531
|
+
try {
|
|
532
|
+
// Apply template
|
|
533
|
+
const applyResponse = await api.taskmaster.applyTemplate(currentProject.name, {
|
|
534
|
+
templateId: selectedTemplate.id,
|
|
535
|
+
fileName,
|
|
536
|
+
customizations
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
if (!applyResponse.ok) {
|
|
540
|
+
const error = await applyResponse.json();
|
|
541
|
+
throw new Error(error.message || 'Failed to apply template');
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Parse PRD to generate tasks
|
|
545
|
+
const parseResponse = await api.taskmaster.parsePRD(currentProject.name, {
|
|
546
|
+
fileName,
|
|
547
|
+
numTasks: 10
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
if (!parseResponse.ok) {
|
|
551
|
+
const error = await parseResponse.json();
|
|
552
|
+
throw new Error(error.message || 'Failed to generate tasks');
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
setStep('generate');
|
|
556
|
+
setTimeout(() => {
|
|
557
|
+
onTemplateApplied();
|
|
558
|
+
}, 2000);
|
|
559
|
+
|
|
560
|
+
} catch (error) {
|
|
561
|
+
console.error('Error applying template:', error);
|
|
562
|
+
alert(`Error: ${error.message}`);
|
|
563
|
+
setIsApplying(false);
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
if (isLoading) {
|
|
568
|
+
return (
|
|
569
|
+
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
|
570
|
+
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 w-full max-w-md">
|
|
571
|
+
<div className="flex items-center gap-3">
|
|
572
|
+
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600"></div>
|
|
573
|
+
<span className="text-gray-900 dark:text-white">Loading templates...</span>
|
|
574
|
+
</div>
|
|
575
|
+
</div>
|
|
576
|
+
</div>
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
return (
|
|
581
|
+
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
|
582
|
+
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 w-full max-w-2xl max-h-[80vh] overflow-y-auto">
|
|
583
|
+
<div className="flex items-center justify-between mb-4">
|
|
584
|
+
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
|
|
585
|
+
{step === 'select' ? 'Select PRD Template' :
|
|
586
|
+
step === 'customize' ? 'Customize Template' :
|
|
587
|
+
'Generating Tasks'}
|
|
588
|
+
</h3>
|
|
589
|
+
<button
|
|
590
|
+
onClick={onClose}
|
|
591
|
+
className="p-1 hover:bg-gray-200 dark:hover:bg-gray-700 rounded"
|
|
592
|
+
>
|
|
593
|
+
<X className="w-4 h-4" />
|
|
594
|
+
</button>
|
|
595
|
+
</div>
|
|
596
|
+
|
|
597
|
+
{step === 'select' && (
|
|
598
|
+
<div className="space-y-3">
|
|
599
|
+
{templates.map((template) => (
|
|
600
|
+
<div
|
|
601
|
+
key={template.id}
|
|
602
|
+
className="p-4 border border-gray-200 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer transition-colors"
|
|
603
|
+
onClick={() => handleSelectTemplate(template)}
|
|
604
|
+
>
|
|
605
|
+
<div className="flex items-start justify-between">
|
|
606
|
+
<div className="flex-1">
|
|
607
|
+
<h4 className="font-medium text-gray-900 dark:text-white">{template.name}</h4>
|
|
608
|
+
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1">{template.description}</p>
|
|
609
|
+
<span className="inline-block text-xs bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 px-2 py-1 rounded mt-2">
|
|
610
|
+
{template.category}
|
|
611
|
+
</span>
|
|
612
|
+
</div>
|
|
613
|
+
<ArrowRight className="w-4 h-4 text-gray-400 mt-1" />
|
|
614
|
+
</div>
|
|
615
|
+
</div>
|
|
616
|
+
))}
|
|
617
|
+
</div>
|
|
618
|
+
)}
|
|
619
|
+
|
|
620
|
+
{step === 'customize' && selectedTemplate && (
|
|
621
|
+
<div className="space-y-4">
|
|
622
|
+
<div>
|
|
623
|
+
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
624
|
+
File Name
|
|
625
|
+
</label>
|
|
626
|
+
<input
|
|
627
|
+
type="text"
|
|
628
|
+
value={fileName}
|
|
629
|
+
onChange={(e) => setFileName(e.target.value)}
|
|
630
|
+
className="w-full p-2 border border-gray-300 dark:border-gray-600 rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
|
631
|
+
placeholder="prd.txt"
|
|
632
|
+
/>
|
|
633
|
+
</div>
|
|
634
|
+
|
|
635
|
+
{Object.keys(customizations).length > 0 && (
|
|
636
|
+
<div>
|
|
637
|
+
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
638
|
+
Customize Template
|
|
639
|
+
</label>
|
|
640
|
+
<div className="space-y-3">
|
|
641
|
+
{Object.entries(customizations).map(([key, value]) => (
|
|
642
|
+
<div key={key}>
|
|
643
|
+
<label className="block text-xs text-gray-600 dark:text-gray-400 mb-1">
|
|
644
|
+
{key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase())}
|
|
645
|
+
</label>
|
|
646
|
+
<input
|
|
647
|
+
type="text"
|
|
648
|
+
value={value}
|
|
649
|
+
onChange={(e) => setCustomizations(prev => ({ ...prev, [key]: e.target.value }))}
|
|
650
|
+
className="w-full p-2 border border-gray-300 dark:border-gray-600 rounded text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
|
651
|
+
placeholder={`Enter ${key.toLowerCase()}`}
|
|
652
|
+
/>
|
|
653
|
+
</div>
|
|
654
|
+
))}
|
|
655
|
+
</div>
|
|
656
|
+
</div>
|
|
657
|
+
)}
|
|
658
|
+
|
|
659
|
+
<div className="flex gap-2 pt-4">
|
|
660
|
+
<button
|
|
661
|
+
onClick={() => setStep('select')}
|
|
662
|
+
className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300"
|
|
663
|
+
>
|
|
664
|
+
Back
|
|
665
|
+
</button>
|
|
666
|
+
<button
|
|
667
|
+
onClick={handleApplyTemplate}
|
|
668
|
+
className="flex-1 px-4 py-2 bg-purple-600 hover:bg-purple-700 text-white rounded disabled:opacity-50"
|
|
669
|
+
disabled={isApplying}
|
|
670
|
+
>
|
|
671
|
+
{isApplying ? 'Applying...' : 'Apply & Generate Tasks'}
|
|
672
|
+
</button>
|
|
673
|
+
</div>
|
|
674
|
+
</div>
|
|
675
|
+
)}
|
|
676
|
+
|
|
677
|
+
{step === 'generate' && (
|
|
678
|
+
<div className="text-center py-8">
|
|
679
|
+
<div className="w-16 h-16 bg-green-100 dark:bg-green-900 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
680
|
+
<CheckCircle className="w-8 h-8 text-green-600 dark:text-green-400" />
|
|
681
|
+
</div>
|
|
682
|
+
<h4 className="text-lg font-medium text-gray-900 dark:text-white mb-2">
|
|
683
|
+
Template Applied Successfully!
|
|
684
|
+
</h4>
|
|
685
|
+
<p className="text-gray-600 dark:text-gray-400">
|
|
686
|
+
Your PRD has been created and tasks are being generated...
|
|
687
|
+
</p>
|
|
688
|
+
</div>
|
|
689
|
+
)}
|
|
690
|
+
</div>
|
|
691
|
+
</div>
|
|
692
|
+
);
|
|
693
|
+
};
|
|
694
|
+
|
|
695
|
+
export default NextTaskBanner;
|