lemming-cli 0.1.0__py3-none-any.whl
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.
- lemming/__init__.py +1 -0
- lemming/api/__init__.py +5 -0
- lemming/api/auth.py +34 -0
- lemming/api/auth_test.py +40 -0
- lemming/api/config.py +85 -0
- lemming/api/config_test.py +84 -0
- lemming/api/conftest.py +204 -0
- lemming/api/context.py +39 -0
- lemming/api/context_test.py +62 -0
- lemming/api/directories.py +57 -0
- lemming/api/directories_test.py +94 -0
- lemming/api/files.py +116 -0
- lemming/api/files_test.py +163 -0
- lemming/api/hooks.py +15 -0
- lemming/api/hooks_test.py +33 -0
- lemming/api/logging.py +16 -0
- lemming/api/logging_test.py +59 -0
- lemming/api/loop.py +45 -0
- lemming/api/loop_test.py +58 -0
- lemming/api/main.py +53 -0
- lemming/api/main_test.py +30 -0
- lemming/api/tasks.py +170 -0
- lemming/api/tasks_test.py +426 -0
- lemming/api.py +5 -0
- lemming/api_test.py +7 -0
- lemming/cli/__init__.py +12 -0
- lemming/cli/config.py +67 -0
- lemming/cli/config_test.py +50 -0
- lemming/cli/context.py +40 -0
- lemming/cli/context_test.py +49 -0
- lemming/cli/hooks.py +147 -0
- lemming/cli/hooks_test.py +50 -0
- lemming/cli/main.py +42 -0
- lemming/cli/main_test.py +21 -0
- lemming/cli/operations.py +226 -0
- lemming/cli/operations_test.py +44 -0
- lemming/cli/progress.py +54 -0
- lemming/cli/progress_test.py +40 -0
- lemming/cli/readability_cli.py +28 -0
- lemming/cli/readability_cli_test.py +57 -0
- lemming/cli/tasks.py +529 -0
- lemming/cli/tasks_test.py +168 -0
- lemming/cli.py +5 -0
- lemming/cli_test.py +22 -0
- lemming/conftest.py +13 -0
- lemming/hooks.py +145 -0
- lemming/hooks_test.py +180 -0
- lemming/integration_test.py +299 -0
- lemming/main.py +6 -0
- lemming/main_test.py +44 -0
- lemming/models.py +88 -0
- lemming/models_test.py +91 -0
- lemming/orchestrator.py +407 -0
- lemming/orchestrator_test.py +468 -0
- lemming/paths.py +245 -0
- lemming/paths_test.py +186 -0
- lemming/persistence.py +179 -0
- lemming/persistence_test.py +150 -0
- lemming/prompts/hooks/readability.md +42 -0
- lemming/prompts/hooks/roadmap.md +61 -0
- lemming/prompts/hooks/testing.md +44 -0
- lemming/prompts/taskrunner.md +69 -0
- lemming/prompts.py +354 -0
- lemming/prompts_test.py +421 -0
- lemming/providers.py +190 -0
- lemming/providers_test.py +73 -0
- lemming/runner.py +327 -0
- lemming/runner_test.py +378 -0
- lemming/tasks/__init__.py +75 -0
- lemming/tasks/lifecycle.py +331 -0
- lemming/tasks/lifecycle_test.py +312 -0
- lemming/tasks/operations.py +258 -0
- lemming/tasks/operations_test.py +172 -0
- lemming/tasks/progress.py +29 -0
- lemming/tasks/progress_test.py +22 -0
- lemming/tasks/queries.py +128 -0
- lemming/tasks/queries_test.py +233 -0
- lemming/web/dashboard.spec.js +350 -0
- lemming/web/dashboard.test.js +998 -0
- lemming/web/favicon.js +40 -0
- lemming/web/favicon.spec.js +242 -0
- lemming/web/files.html +375 -0
- lemming/web/files.spec.js +97 -0
- lemming/web/index.html +983 -0
- lemming/web/index.js +753 -0
- lemming/web/logs.html +358 -0
- lemming/web/logs.test.js +195 -0
- lemming/web/mancha.js +58 -0
- lemming/web/screenshots.spec.js +328 -0
- lemming_cli-0.1.0.dist-info/METADATA +314 -0
- lemming_cli-0.1.0.dist-info/RECORD +94 -0
- lemming_cli-0.1.0.dist-info/WHEEL +4 -0
- lemming_cli-0.1.0.dist-info/entry_points.txt +2 -0
- lemming_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
lemming/web/index.html
ADDED
|
@@ -0,0 +1,983 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Lemming</title>
|
|
7
|
+
<link
|
|
8
|
+
rel="icon"
|
|
9
|
+
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🐹</text></svg>"
|
|
10
|
+
/>
|
|
11
|
+
<style id="mancha-cloak">
|
|
12
|
+
html {
|
|
13
|
+
background-color: #f9fafb; /* Gray 50 */
|
|
14
|
+
}
|
|
15
|
+
@media (prefers-color-scheme: dark) {
|
|
16
|
+
html {
|
|
17
|
+
background-color: #1f2937; /* Gray 800 */
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
body {
|
|
21
|
+
opacity: 0 !important;
|
|
22
|
+
}
|
|
23
|
+
</style>
|
|
24
|
+
<style>
|
|
25
|
+
/* Loading indicator visible during cloaking */
|
|
26
|
+
html.mancha-loading::after {
|
|
27
|
+
content: 'Loading Lemming...';
|
|
28
|
+
position: fixed;
|
|
29
|
+
inset: 0;
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
justify-content: center;
|
|
33
|
+
font-family: ui-sans-serif, system-ui, sans-serif;
|
|
34
|
+
font-size: 1.25rem;
|
|
35
|
+
font-weight: 600;
|
|
36
|
+
color: #4f46e5; /* Indigo 600 */
|
|
37
|
+
z-index: 9999;
|
|
38
|
+
animation: mancha-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@media (prefers-color-scheme: dark) {
|
|
42
|
+
html.mancha-loading::after {
|
|
43
|
+
color: #818cf8; /* Indigo 400 */
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@keyframes mancha-pulse {
|
|
48
|
+
0%,
|
|
49
|
+
100% {
|
|
50
|
+
opacity: 1;
|
|
51
|
+
}
|
|
52
|
+
50% {
|
|
53
|
+
opacity: 0.5;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
</style>
|
|
57
|
+
<script src="/static/mancha.js" css="utils"></script>
|
|
58
|
+
<script src="/static/favicon.js"></script>
|
|
59
|
+
<script src="/static/index.js" defer></script>
|
|
60
|
+
</head>
|
|
61
|
+
<body
|
|
62
|
+
class="bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 font-sans min-h-screen flex flex-col"
|
|
63
|
+
>
|
|
64
|
+
<div class="max-w-3xl mx-auto p-4 flex-grow w-full flex flex-col">
|
|
65
|
+
<div :show="loading" class="flex-grow flex items-center justify-center">
|
|
66
|
+
<span class="text-gray-400 text-sm animate-pulse">Loading...</span>
|
|
67
|
+
</div>
|
|
68
|
+
<header :show="!loading" class="mb-4 shrink-0" role="banner">
|
|
69
|
+
<h1
|
|
70
|
+
class="text-2xl font-bold text-indigo-600 dark:text-indigo-400"
|
|
71
|
+
title="Lemming Task Runner"
|
|
72
|
+
>
|
|
73
|
+
Lemming Task Runner
|
|
74
|
+
</h1>
|
|
75
|
+
</header>
|
|
76
|
+
|
|
77
|
+
<!-- Runner Metadata Card -->
|
|
78
|
+
<section
|
|
79
|
+
:show="!loading"
|
|
80
|
+
class="mb-4 bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden shrink-0"
|
|
81
|
+
aria-labelledby="metadata-heading"
|
|
82
|
+
>
|
|
83
|
+
<div
|
|
84
|
+
class="py-2 px-4 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 flex justify-between items-center"
|
|
85
|
+
>
|
|
86
|
+
<h2
|
|
87
|
+
id="metadata-heading"
|
|
88
|
+
class="text-xs font-semibold text-gray-500 uppercase tracking-wider"
|
|
89
|
+
>
|
|
90
|
+
Runner Metadata
|
|
91
|
+
</h2>
|
|
92
|
+
<div class="flex items-center gap-3">
|
|
93
|
+
<button
|
|
94
|
+
type="submit"
|
|
95
|
+
form="run-form"
|
|
96
|
+
:prop:disabled="loopRunning"
|
|
97
|
+
:attr:class="'px-3 py-1 transition duration-200 border-none rounded-md text-xs font-medium whitespace-nowrap ' + (loopRunning ? 'bg-gray-400 cursor-not-allowed text-gray-200' : 'bg-indigo-600 text-white hover:bg-indigo-700')"
|
|
98
|
+
:attr:title="loopRunning ? 'Tasks are already executing' : 'Execute autonomous tasks'"
|
|
99
|
+
aria-label="Execute Tasks"
|
|
100
|
+
>
|
|
101
|
+
{{ loopRunning ? 'Executing...' : 'Execute Tasks' }}
|
|
102
|
+
</button>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
<div class="p-4 space-y-4">
|
|
106
|
+
<form
|
|
107
|
+
class="flex flex-wrap gap-6 items-start"
|
|
108
|
+
:on:submit.prevent="runLemming()"
|
|
109
|
+
id="run-form"
|
|
110
|
+
aria-label="Run autonomous loop"
|
|
111
|
+
>
|
|
112
|
+
<div class="flex-grow min-w-0 md:min-w-[200px]">
|
|
113
|
+
<div class="flex items-center gap-1 mb-1">
|
|
114
|
+
<label class="text-xs font-semibold text-gray-400 uppercase"
|
|
115
|
+
>Project Directory</label
|
|
116
|
+
>
|
|
117
|
+
<a
|
|
118
|
+
:attr:href="'/files/' + ($$project || '')"
|
|
119
|
+
target="_blank"
|
|
120
|
+
class="p-0 text-gray-400 hover:text-indigo-600 shrink-0 transition duration-200 inline-flex items-center"
|
|
121
|
+
title="Browse files"
|
|
122
|
+
>
|
|
123
|
+
<svg
|
|
124
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
125
|
+
class="h-3 w-3"
|
|
126
|
+
fill="none"
|
|
127
|
+
viewBox="0 0 24 24"
|
|
128
|
+
stroke="currentColor"
|
|
129
|
+
>
|
|
130
|
+
<path
|
|
131
|
+
stroke-linecap="round"
|
|
132
|
+
stroke-linejoin="round"
|
|
133
|
+
stroke-width="2"
|
|
134
|
+
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"
|
|
135
|
+
/>
|
|
136
|
+
</svg>
|
|
137
|
+
</a>
|
|
138
|
+
<button
|
|
139
|
+
type="button"
|
|
140
|
+
:on:click="openFolderPicker()"
|
|
141
|
+
class="p-0 text-gray-400 hover:text-indigo-600 bg-transparent border-none cursor-pointer shrink-0 transition duration-200 inline-flex items-center"
|
|
142
|
+
title="Switch project"
|
|
143
|
+
>
|
|
144
|
+
<svg
|
|
145
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
146
|
+
class="h-3 w-3"
|
|
147
|
+
fill="none"
|
|
148
|
+
viewBox="0 0 24 24"
|
|
149
|
+
stroke="currentColor"
|
|
150
|
+
>
|
|
151
|
+
<path
|
|
152
|
+
stroke-linecap="round"
|
|
153
|
+
stroke-linejoin="round"
|
|
154
|
+
stroke-width="2"
|
|
155
|
+
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
|
|
156
|
+
/>
|
|
157
|
+
</svg>
|
|
158
|
+
</button>
|
|
159
|
+
</div>
|
|
160
|
+
<span class="block text-sm font-mono truncate" :attr:title="cwd"
|
|
161
|
+
>{{ cwd }}</span
|
|
162
|
+
>
|
|
163
|
+
</div>
|
|
164
|
+
<div class="flex-1 min-w-[200px]">
|
|
165
|
+
<label
|
|
166
|
+
class="block text-xs font-semibold text-gray-400 uppercase mb-1"
|
|
167
|
+
>Task Summary</label
|
|
168
|
+
>
|
|
169
|
+
<div
|
|
170
|
+
class="text-sm font-medium text-gray-700 dark:text-gray-300 gap-x-2 flex flex-wrap"
|
|
171
|
+
>
|
|
172
|
+
<span :show="tasks.length === 0" class="text-gray-400"
|
|
173
|
+
>No tasks</span
|
|
174
|
+
>
|
|
175
|
+
<span
|
|
176
|
+
:show="runningCount > 0"
|
|
177
|
+
class="text-blue-600 dark:text-blue-400"
|
|
178
|
+
>{{ runningCount }} running</span
|
|
179
|
+
>
|
|
180
|
+
<span :show="pendingCount > 0" class="text-gray-500"
|
|
181
|
+
>{{ pendingCount }} pending</span
|
|
182
|
+
>
|
|
183
|
+
<span
|
|
184
|
+
:show="completedCount > 0"
|
|
185
|
+
class="text-green-600 dark:text-green-400"
|
|
186
|
+
>{{ completedCount }} done</span
|
|
187
|
+
>
|
|
188
|
+
<span
|
|
189
|
+
:show="failedCount > 0"
|
|
190
|
+
class="text-red-600 dark:text-red-400"
|
|
191
|
+
>{{ failedCount }} failed</span
|
|
192
|
+
>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
<!-- Runner Command -->
|
|
196
|
+
<div class="flex-1 min-w-[180px]">
|
|
197
|
+
<label
|
|
198
|
+
class="block text-xs font-semibold text-gray-400 uppercase mb-1"
|
|
199
|
+
>Runner Command</label
|
|
200
|
+
>
|
|
201
|
+
<input
|
|
202
|
+
type="text"
|
|
203
|
+
list="runner-options"
|
|
204
|
+
:bind="selectedRunner"
|
|
205
|
+
:on:change="saveRunnerPreference()"
|
|
206
|
+
placeholder="Runner command..."
|
|
207
|
+
title="Runner command to use for the autonomous loop"
|
|
208
|
+
aria-label="Runner command"
|
|
209
|
+
class="px-2 py-1 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-md text-sm focus:ring-indigo-500 focus:border-indigo-500 w-full"
|
|
210
|
+
/>
|
|
211
|
+
<datalist id="runner-options">
|
|
212
|
+
<option :for="r in runners" :key="r" :attr:value="r"></option>
|
|
213
|
+
</datalist>
|
|
214
|
+
</div>
|
|
215
|
+
<!-- Retries -->
|
|
216
|
+
<div class="w-24 shrink-0">
|
|
217
|
+
<label
|
|
218
|
+
class="block text-xs font-semibold text-gray-400 uppercase mb-1"
|
|
219
|
+
>Retries</label
|
|
220
|
+
>
|
|
221
|
+
<input
|
|
222
|
+
type="number"
|
|
223
|
+
min="1"
|
|
224
|
+
:bind="retries"
|
|
225
|
+
:on:change="saveRetriesPreference()"
|
|
226
|
+
placeholder="Max"
|
|
227
|
+
title="Maximum number of retries per task"
|
|
228
|
+
aria-label="Retries"
|
|
229
|
+
class="px-2 py-1 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-md text-sm focus:ring-indigo-500 focus:border-indigo-500 w-full"
|
|
230
|
+
/>
|
|
231
|
+
</div>
|
|
232
|
+
<!-- Time Limit -->
|
|
233
|
+
<div class="w-28 shrink-0">
|
|
234
|
+
<label
|
|
235
|
+
class="block text-xs font-semibold text-gray-400 uppercase mb-1"
|
|
236
|
+
>Time Limit</label
|
|
237
|
+
>
|
|
238
|
+
<select
|
|
239
|
+
:bind="timeLimit"
|
|
240
|
+
:on:change="saveTimeLimitPreference()"
|
|
241
|
+
title="Max execution time per task"
|
|
242
|
+
aria-label="Time limit"
|
|
243
|
+
class="px-2 py-1 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-md text-sm focus:ring-indigo-500 focus:border-indigo-500 w-full"
|
|
244
|
+
>
|
|
245
|
+
<option value="0">None</option>
|
|
246
|
+
<option value="15">15m</option>
|
|
247
|
+
<option value="30">30m</option>
|
|
248
|
+
<option value="60">1h</option>
|
|
249
|
+
<option value="120">2h</option>
|
|
250
|
+
<option value="240">4h</option>
|
|
251
|
+
</select>
|
|
252
|
+
</div>
|
|
253
|
+
</form>
|
|
254
|
+
|
|
255
|
+
<div class="pt-2 border-t border-gray-100 dark:border-gray-700">
|
|
256
|
+
<div class="flex items-baseline justify-between mb-2">
|
|
257
|
+
<label class="block text-xs font-semibold text-gray-400 uppercase"
|
|
258
|
+
>Orchestrator Hooks</label
|
|
259
|
+
>
|
|
260
|
+
<button
|
|
261
|
+
:show="config.hooks !== null"
|
|
262
|
+
type="button"
|
|
263
|
+
role="button"
|
|
264
|
+
:on:click="resetHooks()"
|
|
265
|
+
class="text-[10px] font-semibold text-indigo-600 hover:text-indigo-800 uppercase tracking-wider bg-transparent border-none cursor-pointer p-0 transition duration-200"
|
|
266
|
+
title="Reset hooks to default (all available)"
|
|
267
|
+
aria-label="Reset hooks"
|
|
268
|
+
>
|
|
269
|
+
Reset
|
|
270
|
+
</button>
|
|
271
|
+
</div>
|
|
272
|
+
<div class="flex flex-wrap items-center gap-4">
|
|
273
|
+
<label
|
|
274
|
+
:for="hook in availableHooks"
|
|
275
|
+
:key="hook"
|
|
276
|
+
class="flex items-center gap-2 cursor-pointer shrink-0"
|
|
277
|
+
:attr:title="'Run the ' + hook + ' orchestrator hook after each task'"
|
|
278
|
+
>
|
|
279
|
+
<input
|
|
280
|
+
type="checkbox"
|
|
281
|
+
:prop:checked="config.hooks === null ? true : config.hooks.includes(hook)"
|
|
282
|
+
:on:change="toggleHook(hook)"
|
|
283
|
+
class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 h-4 w-4"
|
|
284
|
+
:attr:aria-label="'Enable ' + hook + ' hook'"
|
|
285
|
+
/>
|
|
286
|
+
<span
|
|
287
|
+
class="text-sm font-medium text-gray-700 dark:text-gray-300"
|
|
288
|
+
>{{ hook }}</span
|
|
289
|
+
>
|
|
290
|
+
</label>
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
293
|
+
|
|
294
|
+
<div class="pt-2 border-t border-gray-100 dark:border-gray-700">
|
|
295
|
+
<label
|
|
296
|
+
class="block text-xs font-semibold text-gray-400 uppercase mb-2"
|
|
297
|
+
>Environment Overrides</label
|
|
298
|
+
>
|
|
299
|
+
<div class="space-y-2">
|
|
300
|
+
<div
|
|
301
|
+
:for="override in envOverrides"
|
|
302
|
+
:key="override.id"
|
|
303
|
+
class="flex gap-2"
|
|
304
|
+
>
|
|
305
|
+
<input
|
|
306
|
+
type="text"
|
|
307
|
+
:bind="override.key"
|
|
308
|
+
:on:input="saveEnvOverrides()"
|
|
309
|
+
placeholder="KEY (e.g. OPENAI_API_KEY)"
|
|
310
|
+
class="flex-1 px-2 py-1 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-md text-sm font-mono focus:ring-indigo-500 focus:border-indigo-500"
|
|
311
|
+
/>
|
|
312
|
+
<input
|
|
313
|
+
type="text"
|
|
314
|
+
:bind="override.value"
|
|
315
|
+
:on:input="saveEnvOverrides()"
|
|
316
|
+
placeholder="VALUE"
|
|
317
|
+
class="flex-1 px-2 py-1 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-md text-sm font-mono focus:ring-indigo-500 focus:border-indigo-500"
|
|
318
|
+
/>
|
|
319
|
+
<button
|
|
320
|
+
type="button"
|
|
321
|
+
role="button"
|
|
322
|
+
:on:click="removeEnvOverride($index)"
|
|
323
|
+
class="px-2 py-1 text-red-500 hover:bg-red-50 dark:hover:bg-red-900/30 transition duration-200 rounded-md border-none bg-transparent cursor-pointer font-bold"
|
|
324
|
+
title="Remove override"
|
|
325
|
+
aria-label="Remove override"
|
|
326
|
+
>
|
|
327
|
+
×
|
|
328
|
+
</button>
|
|
329
|
+
</div>
|
|
330
|
+
<button
|
|
331
|
+
type="button"
|
|
332
|
+
role="button"
|
|
333
|
+
:on:click="addEnvOverride()"
|
|
334
|
+
class="text-xs font-semibold text-indigo-600 hover:text-indigo-800 uppercase tracking-wider bg-transparent border-none cursor-pointer p-0 transition duration-200"
|
|
335
|
+
title="Add new environment variable override"
|
|
336
|
+
aria-label="Add override"
|
|
337
|
+
>
|
|
338
|
+
+ Add Override
|
|
339
|
+
</button>
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
|
|
343
|
+
<div>
|
|
344
|
+
<label
|
|
345
|
+
class="block text-xs font-semibold text-gray-400 uppercase mb-1"
|
|
346
|
+
>Add New Task</label
|
|
347
|
+
>
|
|
348
|
+
<form
|
|
349
|
+
class="flex gap-2"
|
|
350
|
+
:on:submit.prevent="addTask()"
|
|
351
|
+
aria-label="Add new task"
|
|
352
|
+
>
|
|
353
|
+
<input
|
|
354
|
+
type="text"
|
|
355
|
+
:bind="newTask"
|
|
356
|
+
placeholder="Task description..."
|
|
357
|
+
title="Enter task description"
|
|
358
|
+
aria-label="New task description"
|
|
359
|
+
class="flex-1 px-2 py-1 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-md text-sm focus:ring-indigo-500 focus:border-indigo-500"
|
|
360
|
+
/>
|
|
361
|
+
<button
|
|
362
|
+
type="submit"
|
|
363
|
+
role="button"
|
|
364
|
+
:prop:disabled="!newTask || !newTask.trim()"
|
|
365
|
+
:attr:class="'px-3 py-1 transition duration-200 border-none rounded-md text-sm font-medium whitespace-nowrap ' + (!newTask || !newTask.trim() ? 'bg-gray-400 cursor-not-allowed text-gray-200' : 'bg-indigo-600 text-white hover:bg-indigo-700')"
|
|
366
|
+
title="Add new task"
|
|
367
|
+
aria-label="Add task"
|
|
368
|
+
>
|
|
369
|
+
Add Task
|
|
370
|
+
</button>
|
|
371
|
+
</form>
|
|
372
|
+
</div>
|
|
373
|
+
</div>
|
|
374
|
+
</section>
|
|
375
|
+
|
|
376
|
+
<section
|
|
377
|
+
:show="!loading"
|
|
378
|
+
class="mb-4 bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden shrink-0"
|
|
379
|
+
aria-labelledby="context-heading"
|
|
380
|
+
>
|
|
381
|
+
<div
|
|
382
|
+
class="py-2 px-4 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900"
|
|
383
|
+
>
|
|
384
|
+
<h2
|
|
385
|
+
id="context-heading"
|
|
386
|
+
class="text-xs font-semibold text-gray-500 uppercase tracking-wider"
|
|
387
|
+
>
|
|
388
|
+
Project Context
|
|
389
|
+
</h2>
|
|
390
|
+
</div>
|
|
391
|
+
<div class="p-4">
|
|
392
|
+
<textarea
|
|
393
|
+
:bind="context"
|
|
394
|
+
:on:input="updateContext()"
|
|
395
|
+
class="w-full h-24 p-2 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-md focus:ring-indigo-500 focus:border-indigo-500 font-mono text-sm"
|
|
396
|
+
placeholder="Enter project guidelines and context..."
|
|
397
|
+
title="Project Context and Guidelines"
|
|
398
|
+
aria-label="Project context and guidelines"
|
|
399
|
+
></textarea>
|
|
400
|
+
</div>
|
|
401
|
+
</section>
|
|
402
|
+
|
|
403
|
+
<section
|
|
404
|
+
:show="!loading"
|
|
405
|
+
class="bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden flex flex-col grow min-h-0"
|
|
406
|
+
aria-labelledby="tasks-heading"
|
|
407
|
+
>
|
|
408
|
+
<div
|
|
409
|
+
class="py-2 px-4 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center bg-gray-50 dark:bg-gray-900 shrink-0"
|
|
410
|
+
>
|
|
411
|
+
<div class="flex items-center gap-3 flex-grow">
|
|
412
|
+
<h2
|
|
413
|
+
id="tasks-heading"
|
|
414
|
+
class="text-xs font-semibold text-gray-500 uppercase tracking-wider shrink-0"
|
|
415
|
+
>
|
|
416
|
+
Task Queue
|
|
417
|
+
</h2>
|
|
418
|
+
<label
|
|
419
|
+
class="flex items-center gap-1 cursor-pointer shrink-0"
|
|
420
|
+
title="Hide or show completed tasks"
|
|
421
|
+
>
|
|
422
|
+
<input
|
|
423
|
+
type="checkbox"
|
|
424
|
+
:bind="hideCompleted"
|
|
425
|
+
:on:change="saveHideCompletedPreference()"
|
|
426
|
+
class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 h-3 w-3"
|
|
427
|
+
aria-label="Hide completed tasks"
|
|
428
|
+
/>
|
|
429
|
+
<span
|
|
430
|
+
class="font-medium text-gray-500 uppercase tracking-wider text-10px"
|
|
431
|
+
>Hide Completed</span
|
|
432
|
+
>
|
|
433
|
+
</label>
|
|
434
|
+
</div>
|
|
435
|
+
<button
|
|
436
|
+
:show="completedCount > 0"
|
|
437
|
+
type="button"
|
|
438
|
+
role="button"
|
|
439
|
+
:on:click="deleteCompletedTasks()"
|
|
440
|
+
class="text-xs font-semibold text-red-500 hover:text-red-700 uppercase tracking-wider bg-transparent border-none cursor-pointer"
|
|
441
|
+
title="Delete all completed tasks"
|
|
442
|
+
aria-label="Delete all completed tasks"
|
|
443
|
+
>
|
|
444
|
+
Delete Completed
|
|
445
|
+
</button>
|
|
446
|
+
</div>
|
|
447
|
+
|
|
448
|
+
<div class="grow overflow-auto" role="list">
|
|
449
|
+
<div
|
|
450
|
+
:for="task in filteredTasks"
|
|
451
|
+
:key="task.id"
|
|
452
|
+
class="flex flex-col border-b border-gray-200 dark:border-gray-600"
|
|
453
|
+
role="listitem"
|
|
454
|
+
>
|
|
455
|
+
<div
|
|
456
|
+
class="pt-1 pb-2 px-4 flex items-center justify-between"
|
|
457
|
+
:attr:aria-label="'Task: ' + task.description"
|
|
458
|
+
:attr:title="'Task: ' + task.description"
|
|
459
|
+
>
|
|
460
|
+
<div
|
|
461
|
+
class="flex items-center gap-2 overflow-hidden flex-grow min-w-0"
|
|
462
|
+
>
|
|
463
|
+
<button
|
|
464
|
+
type="button"
|
|
465
|
+
role="button"
|
|
466
|
+
:on:click="expanded[task.id] = !expanded[task.id]"
|
|
467
|
+
class="p-1 text-gray-600 dark:text-gray-400 hover:text-indigo-600 hover:bg-gray-100 dark:hover:bg-gray-700 transition duration-200 rounded-md inline-flex items-center justify-center bg-transparent border-none cursor-pointer"
|
|
468
|
+
:attr:title="expanded[task.id] ? 'Hide details' : 'Show details'"
|
|
469
|
+
:attr:aria-label="expanded[task.id] ? 'Hide details' : 'Show details'"
|
|
470
|
+
:attr:aria-expanded="expanded[task.id]"
|
|
471
|
+
>
|
|
472
|
+
<svg
|
|
473
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
474
|
+
:attr:class="'h-3 w-3 transition-transform '"
|
|
475
|
+
:attr:style="expanded[task.id] ? 'transform: rotate(180deg)' : ''"
|
|
476
|
+
fill="none"
|
|
477
|
+
viewBox="0 0 24 24"
|
|
478
|
+
stroke="currentColor"
|
|
479
|
+
aria-hidden="true"
|
|
480
|
+
role="img"
|
|
481
|
+
>
|
|
482
|
+
<path
|
|
483
|
+
stroke-linecap="round"
|
|
484
|
+
stroke-linejoin="round"
|
|
485
|
+
stroke-width="2"
|
|
486
|
+
d="M19 9l-7 7-7-7"
|
|
487
|
+
/>
|
|
488
|
+
</svg>
|
|
489
|
+
</button>
|
|
490
|
+
<div class="overflow-hidden flex-grow min-w-0">
|
|
491
|
+
<p
|
|
492
|
+
:attr:class="'text-sm font-medium truncate ' + (task.status === 'completed' ? 'text-gray-400 line-through' : (task.status === 'cancelled' ? 'text-orange-600 line-through' : (task.status === 'in_progress' ? 'text-blue-600' : (task.attempts > 0 ? 'text-red-600' : ''))))"
|
|
493
|
+
:attr:title="task.description"
|
|
494
|
+
>
|
|
495
|
+
{{ task.description }}
|
|
496
|
+
</p>
|
|
497
|
+
<div class="flex items-center gap-2">
|
|
498
|
+
<span
|
|
499
|
+
role="status"
|
|
500
|
+
class="px-2 py-0.5 rounded-full text-xs font-semibold shrink-0 text-center min-w-20 inline-block"
|
|
501
|
+
:class="task.status === 'completed' ? 'bg-green-100 text-green-700' : (task.status === 'failed' ? 'bg-red-100 text-red-700' : (task.status === 'cancelled' ? 'bg-orange-100 text-orange-700' : (task.status === 'in_progress' ? 'bg-blue-100 text-blue-700 animate-pulse' : (task.attempts > 0 ? 'bg-red-100 text-red-700' : 'bg-gray-100 text-gray-500'))))"
|
|
502
|
+
:attr:aria-label="task.status === 'completed' ? 'Completed' : (task.status === 'failed' ? 'Failed' : (task.status === 'cancelled' ? 'Cancelled' : (task.status === 'in_progress' ? (task.requested_status ? 'Finalizing' : 'Running') : (task.attempts > 0 ? 'Failed' : 'Pending'))))"
|
|
503
|
+
:attr:title="task.status === 'completed' ? 'Status: Completed' : (task.status === 'failed' ? 'Status: Failed' : (task.status === 'cancelled' ? 'Status: Cancelled' : (task.status === 'in_progress' ? (task.requested_status ? 'Status: Finalizing (hooks running)' : 'Status: Running') : (task.attempts > 0 ? 'Status: Failed' : 'Status: Pending'))))"
|
|
504
|
+
>
|
|
505
|
+
{{ task.status === 'completed' ? 'Completed' :
|
|
506
|
+
(task.status === 'failed' ? 'Failed' : (task.status ===
|
|
507
|
+
'cancelled' ? 'Cancelled' : (task.status === 'in_progress'
|
|
508
|
+
? (task.requested_status ? 'Finalizing' : 'Running') :
|
|
509
|
+
(task.attempts > 0 ? 'Failed' : 'Pending')))) }}
|
|
510
|
+
</span>
|
|
511
|
+
<p
|
|
512
|
+
class="text-gray-400 font-mono uppercase text-10px flex items-center flex-wrap gap-1 whitespace-nowrap"
|
|
513
|
+
:attr:aria-label="'Task ID ' + task.id + ', ' + task.attempts + ' attempts'"
|
|
514
|
+
title="Task ID and number of attempts"
|
|
515
|
+
>
|
|
516
|
+
<span class="inline-flex items-center gap-1">
|
|
517
|
+
{{ task.id }}
|
|
518
|
+
<button
|
|
519
|
+
type="button"
|
|
520
|
+
role="button"
|
|
521
|
+
:on:click="copyToClipboard(task.id)"
|
|
522
|
+
class="p-0.5 text-gray-400 hover:text-indigo-600 hover:bg-gray-100 transition duration-200 rounded-md inline-flex items-center justify-center bg-transparent border-none cursor-pointer"
|
|
523
|
+
title="Copy Task ID"
|
|
524
|
+
aria-label="Copy Task ID"
|
|
525
|
+
>
|
|
526
|
+
<svg
|
|
527
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
528
|
+
class="h-2.5 w-2.5"
|
|
529
|
+
fill="none"
|
|
530
|
+
viewBox="0 0 24 24"
|
|
531
|
+
stroke="currentColor"
|
|
532
|
+
>
|
|
533
|
+
<path
|
|
534
|
+
stroke-linecap="round"
|
|
535
|
+
stroke-linejoin="round"
|
|
536
|
+
stroke-width="2"
|
|
537
|
+
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
|
|
538
|
+
/>
|
|
539
|
+
</svg>
|
|
540
|
+
</button>
|
|
541
|
+
</span>
|
|
542
|
+
<span>· Attempts: {{ task.attempts }}</span>
|
|
543
|
+
<span
|
|
544
|
+
:if="task.parent"
|
|
545
|
+
:attr:title="'Parent Task: ' + (getParent(task.parent)?.description || task.parent)"
|
|
546
|
+
>
|
|
547
|
+
· Parent: {{ task.parent }}
|
|
548
|
+
</span>
|
|
549
|
+
</p>
|
|
550
|
+
</div>
|
|
551
|
+
</div>
|
|
552
|
+
</div>
|
|
553
|
+
<div class="flex gap-1 shrink-0">
|
|
554
|
+
<button
|
|
555
|
+
type="button"
|
|
556
|
+
role="button"
|
|
557
|
+
:on:click="openTaskActionModal(task.id)"
|
|
558
|
+
class="p-1 text-gray-600 dark:text-gray-400 hover:text-indigo-600 hover:bg-gray-100 dark:hover:bg-gray-700 transition duration-200 rounded-md inline-flex items-center justify-center bg-transparent border-none cursor-pointer"
|
|
559
|
+
title="Task Actions"
|
|
560
|
+
aria-label="Task Actions"
|
|
561
|
+
>
|
|
562
|
+
<svg
|
|
563
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
564
|
+
class="h-4 w-4"
|
|
565
|
+
fill="none"
|
|
566
|
+
viewBox="0 0 24 24"
|
|
567
|
+
stroke="currentColor"
|
|
568
|
+
aria-hidden="true"
|
|
569
|
+
role="img"
|
|
570
|
+
>
|
|
571
|
+
<path
|
|
572
|
+
stroke-linecap="round"
|
|
573
|
+
stroke-linejoin="round"
|
|
574
|
+
stroke-width="2"
|
|
575
|
+
d="M12 5v.01M12 12v.01M12 19v.01"
|
|
576
|
+
/>
|
|
577
|
+
</svg>
|
|
578
|
+
</button>
|
|
579
|
+
</div>
|
|
580
|
+
</div>
|
|
581
|
+
|
|
582
|
+
<!-- Task Details -->
|
|
583
|
+
<div
|
|
584
|
+
:show="expanded[task.id]"
|
|
585
|
+
class="px-12 pb-3 text-xs text-gray-600 dark:text-gray-400 border-t border-gray-100 dark:border-gray-700"
|
|
586
|
+
>
|
|
587
|
+
<div
|
|
588
|
+
class="mt-2 text-gray-700 dark:text-gray-300 font-normal break-words whitespace-pre-wrap"
|
|
589
|
+
:text="task.description"
|
|
590
|
+
></div>
|
|
591
|
+
<div class="mt-2 flex flex-wrap gap-4">
|
|
592
|
+
<div :if="task.runner">
|
|
593
|
+
<span
|
|
594
|
+
class="font-semibold uppercase text-gray-400 block mb-1 text-9px"
|
|
595
|
+
>Custom Runner:</span
|
|
596
|
+
>
|
|
597
|
+
<span
|
|
598
|
+
class="font-mono bg-gray-200 dark:bg-gray-600 px-1 rounded"
|
|
599
|
+
>{{ task.runner }}</span
|
|
600
|
+
>
|
|
601
|
+
</div>
|
|
602
|
+
<div :if="task.pid">
|
|
603
|
+
<span
|
|
604
|
+
class="font-semibold uppercase text-gray-400 block mb-1 text-9px"
|
|
605
|
+
>Process PID:</span
|
|
606
|
+
>
|
|
607
|
+
<span class="font-mono">{{ task.pid }}</span>
|
|
608
|
+
</div>
|
|
609
|
+
<div :if="task.status === 'in_progress' && task.started_at">
|
|
610
|
+
<span
|
|
611
|
+
class="font-semibold uppercase text-gray-400 block mb-1 text-9px"
|
|
612
|
+
>Started At:</span
|
|
613
|
+
>
|
|
614
|
+
<span class="text-gray-700 dark:text-gray-300"
|
|
615
|
+
>{{ formatDate(task.started_at) }}</span
|
|
616
|
+
>
|
|
617
|
+
</div>
|
|
618
|
+
<div :if="task.status === 'completed' && task.completed_at">
|
|
619
|
+
<span
|
|
620
|
+
class="font-semibold uppercase text-gray-400 block mb-1 text-9px"
|
|
621
|
+
>Completed At:</span
|
|
622
|
+
>
|
|
623
|
+
<span class="text-gray-700 dark:text-gray-300"
|
|
624
|
+
>{{ formatDate(task.completed_at) }}</span
|
|
625
|
+
>
|
|
626
|
+
</div>
|
|
627
|
+
<div :if="task.run_time || task.status === 'in_progress'">
|
|
628
|
+
<span
|
|
629
|
+
class="font-semibold uppercase text-gray-400 block mb-1 text-9px"
|
|
630
|
+
>Run Time:</span
|
|
631
|
+
>
|
|
632
|
+
<span class="text-gray-700 dark:text-gray-300"
|
|
633
|
+
>{{ formatTaskRunTime(task) }}</span
|
|
634
|
+
>
|
|
635
|
+
</div>
|
|
636
|
+
<div :if="task.has_runner_log || task.status === 'in_progress'">
|
|
637
|
+
<span
|
|
638
|
+
class="font-semibold uppercase text-gray-400 block mb-1 text-9px"
|
|
639
|
+
>Logs:</span
|
|
640
|
+
>
|
|
641
|
+
<div class="flex items-center gap-3">
|
|
642
|
+
<a
|
|
643
|
+
:if="task.has_runner_log || task.status === 'in_progress'"
|
|
644
|
+
:attr:href="'/tasks/' + task.id + '/log?name=runner' + ($$project ? '&project=' + encodeURIComponent($$project) : '')"
|
|
645
|
+
target="_blank"
|
|
646
|
+
class="text-indigo-600 hover:text-indigo-800 font-medium transition duration-200"
|
|
647
|
+
title="View runner log (includes orchestrator hooks)"
|
|
648
|
+
>Runner</a
|
|
649
|
+
>
|
|
650
|
+
</div>
|
|
651
|
+
</div>
|
|
652
|
+
</div>
|
|
653
|
+
<div :if="task.parent" class="mt-2">
|
|
654
|
+
<span
|
|
655
|
+
class="font-semibold uppercase text-gray-400 block mb-1 text-9px"
|
|
656
|
+
>Parent Task:</span
|
|
657
|
+
>
|
|
658
|
+
<div class="flex items-center gap-2">
|
|
659
|
+
<span
|
|
660
|
+
:attr:class="'font-mono px-2 py-0.5 rounded text-10px font-medium ' + (getParent(task.parent)?.status === 'completed' ? 'bg-green-100 text-green-700' : (getParent(task.parent)?.status === 'cancelled' ? 'bg-orange-100 text-orange-700' : (getParent(task.parent)?.status === 'in_progress' ? 'bg-blue-100 text-blue-700 animate-pulse' : (getParent(task.parent)?.attempts > 0 ? 'bg-red-100 text-red-700' : 'bg-gray-100 text-gray-500'))))"
|
|
661
|
+
>{{ task.parent }}</span
|
|
662
|
+
>
|
|
663
|
+
<span
|
|
664
|
+
:if="getParent(task.parent)"
|
|
665
|
+
class="text-gray-500 italic truncate"
|
|
666
|
+
:attr:title="getParent(task.parent).description"
|
|
667
|
+
>{{ getParent(task.parent).description }}</span
|
|
668
|
+
>
|
|
669
|
+
</div>
|
|
670
|
+
</div>
|
|
671
|
+
<div :show="task.progress?.length > 0" class="mt-3">
|
|
672
|
+
<span
|
|
673
|
+
class="font-semibold uppercase text-gray-400 block mb-1 text-9px"
|
|
674
|
+
>Progress:</span
|
|
675
|
+
>
|
|
676
|
+
<ul class="list-disc ml-4 space-y-1 mb-2">
|
|
677
|
+
<li
|
|
678
|
+
:for="entry in task.progress"
|
|
679
|
+
:key="task.id + '-' + $index"
|
|
680
|
+
class="text-gray-700 dark:text-gray-300 whitespace-pre-wrap"
|
|
681
|
+
:text="entry"
|
|
682
|
+
></li>
|
|
683
|
+
</ul>
|
|
684
|
+
</div>
|
|
685
|
+
</div>
|
|
686
|
+
</div>
|
|
687
|
+
<div
|
|
688
|
+
:show="tasks.length === 0"
|
|
689
|
+
class="p-8 text-center text-gray-500 text-sm"
|
|
690
|
+
role="status"
|
|
691
|
+
title="No tasks available"
|
|
692
|
+
>
|
|
693
|
+
No tasks yet. Add one to get started!
|
|
694
|
+
</div>
|
|
695
|
+
</div>
|
|
696
|
+
</section>
|
|
697
|
+
</div>
|
|
698
|
+
|
|
699
|
+
<div
|
|
700
|
+
class="fixed bottom-4 right-4 flex flex-col items-end gap-2 z-50 pointer-events-none"
|
|
701
|
+
role="log"
|
|
702
|
+
aria-live="polite"
|
|
703
|
+
>
|
|
704
|
+
<div
|
|
705
|
+
:for="toast in toasts"
|
|
706
|
+
:key="toast.id"
|
|
707
|
+
:attr:class="'px-4 py-2 rounded-lg shadow-lg text-white text-sm pointer-events-auto min-w-32 max-w-72 sm:max-w-80 flex justify-between items-center gap-3 transition-all duration-300 backdrop-blur ' + (toast.type === 'success' ? 'bg-green-700' : (toast.type === 'error' ? 'bg-red-700' : 'bg-indigo-700'))"
|
|
708
|
+
role="alert"
|
|
709
|
+
>
|
|
710
|
+
<span
|
|
711
|
+
class="truncate flex-1 min-w-0"
|
|
712
|
+
:attr:aria-label="toast.message"
|
|
713
|
+
:attr:title="toast.message"
|
|
714
|
+
>{{ toast.message }}</span
|
|
715
|
+
>
|
|
716
|
+
<button
|
|
717
|
+
type="button"
|
|
718
|
+
role="button"
|
|
719
|
+
:on:click="toasts = toasts.filter((t) => (t.id !== toast.id))"
|
|
720
|
+
class="text-white hover:text-gray-200 font-bold shrink-0"
|
|
721
|
+
title="Dismiss notification"
|
|
722
|
+
aria-label="Dismiss notification"
|
|
723
|
+
>
|
|
724
|
+
<span aria-hidden="true">×</span>
|
|
725
|
+
</button>
|
|
726
|
+
</div>
|
|
727
|
+
</div>
|
|
728
|
+
<dialog
|
|
729
|
+
id="task-action-modal"
|
|
730
|
+
class="p-6 rounded-xl shadow-2xl backdrop:bg-black/50 backdrop:backdrop-blur-sm border border-gray-100 dark:border-gray-700 w-full max-w-sm bg-white dark:bg-gray-900 dark:text-gray-100"
|
|
731
|
+
>
|
|
732
|
+
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
|
733
|
+
Task Action
|
|
734
|
+
</h3>
|
|
735
|
+
<p class="text-sm text-gray-500 dark:text-gray-400 mb-6">
|
|
736
|
+
Choose an action for this task.
|
|
737
|
+
</p>
|
|
738
|
+
<div class="flex flex-col gap-3">
|
|
739
|
+
<button
|
|
740
|
+
type="button"
|
|
741
|
+
:on:click="editTaskFromModal()"
|
|
742
|
+
:show="taskActionTargetStatus === 'pending'"
|
|
743
|
+
class="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-md shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 transition duration-200 cursor-pointer"
|
|
744
|
+
>
|
|
745
|
+
Edit Task
|
|
746
|
+
</button>
|
|
747
|
+
<button
|
|
748
|
+
type="button"
|
|
749
|
+
:on:click="confirmReopenTask()"
|
|
750
|
+
:show="taskActionTargetStatus === 'completed' || taskActionTargetStatus === 'failed' || taskActionTargetStatus === 'cancelled'"
|
|
751
|
+
class="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-md shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 transition duration-200 cursor-pointer"
|
|
752
|
+
>
|
|
753
|
+
Re-open Task
|
|
754
|
+
</button>
|
|
755
|
+
<button
|
|
756
|
+
type="button"
|
|
757
|
+
:on:click="confirmCancelTask()"
|
|
758
|
+
:show="taskActionTargetStatus === 'pending' || taskActionTargetStatus === 'in_progress'"
|
|
759
|
+
class="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-md shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 transition duration-200 cursor-pointer"
|
|
760
|
+
>
|
|
761
|
+
Cancel Task
|
|
762
|
+
</button>
|
|
763
|
+
<button
|
|
764
|
+
type="button"
|
|
765
|
+
:on:click="clearTaskFromModal()"
|
|
766
|
+
:show="taskActionTargetStatus !== 'completed' && taskActionTargetStatus !== 'in_progress' && taskActionTargetHasAttempts"
|
|
767
|
+
class="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-md shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 transition duration-200 cursor-pointer"
|
|
768
|
+
>
|
|
769
|
+
Clear Attempts
|
|
770
|
+
</button>
|
|
771
|
+
<button
|
|
772
|
+
type="button"
|
|
773
|
+
:on:click="confirmDeleteTask()"
|
|
774
|
+
:show="taskActionTargetStatus !== 'in_progress'"
|
|
775
|
+
class="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-md shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 transition duration-200 cursor-pointer"
|
|
776
|
+
>
|
|
777
|
+
Delete Task
|
|
778
|
+
</button>
|
|
779
|
+
<button
|
|
780
|
+
type="button"
|
|
781
|
+
:on:click="closeTaskActionModal()"
|
|
782
|
+
class="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-md shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 transition duration-200 cursor-pointer"
|
|
783
|
+
>
|
|
784
|
+
Dismiss
|
|
785
|
+
</button>
|
|
786
|
+
</div>
|
|
787
|
+
</dialog>
|
|
788
|
+
<dialog
|
|
789
|
+
id="edit-modal"
|
|
790
|
+
class="p-6 rounded-xl shadow-2xl backdrop:bg-black/50 backdrop:backdrop-blur-sm border border-gray-100 dark:border-gray-700 w-full max-w-lg bg-white dark:bg-gray-900 dark:text-gray-100"
|
|
791
|
+
>
|
|
792
|
+
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
|
793
|
+
Edit Task
|
|
794
|
+
</h3>
|
|
795
|
+
<form
|
|
796
|
+
method="dialog"
|
|
797
|
+
class="flex flex-col gap-4"
|
|
798
|
+
:on:submit.prevent="submitEditTask()"
|
|
799
|
+
>
|
|
800
|
+
<div>
|
|
801
|
+
<label
|
|
802
|
+
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
|
|
803
|
+
>Description</label
|
|
804
|
+
>
|
|
805
|
+
<textarea
|
|
806
|
+
:bind="editFormData.description"
|
|
807
|
+
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
|
808
|
+
rows="3"
|
|
809
|
+
required
|
|
810
|
+
></textarea>
|
|
811
|
+
</div>
|
|
812
|
+
<div>
|
|
813
|
+
<label
|
|
814
|
+
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
|
|
815
|
+
>Parent Task ID (optional)</label
|
|
816
|
+
>
|
|
817
|
+
<input
|
|
818
|
+
type="text"
|
|
819
|
+
:bind="editFormData.parent"
|
|
820
|
+
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
|
821
|
+
/>
|
|
822
|
+
</div>
|
|
823
|
+
<div class="mt-2 flex justify-end gap-3">
|
|
824
|
+
<button
|
|
825
|
+
type="button"
|
|
826
|
+
:on:click="closeEditModal()"
|
|
827
|
+
class="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 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
|
828
|
+
>
|
|
829
|
+
Cancel
|
|
830
|
+
</button>
|
|
831
|
+
<button
|
|
832
|
+
type="submit"
|
|
833
|
+
class="px-4 py-2 text-sm font-medium text-white bg-indigo-600 border border-transparent rounded-md shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
|
834
|
+
>
|
|
835
|
+
Save
|
|
836
|
+
</button>
|
|
837
|
+
</div>
|
|
838
|
+
</form>
|
|
839
|
+
</dialog>
|
|
840
|
+
<dialog
|
|
841
|
+
id="folder-picker-modal"
|
|
842
|
+
class="p-6 rounded-xl shadow-2xl backdrop:bg-black/50 backdrop:backdrop-blur-sm border border-gray-100 dark:border-gray-700 w-full max-w-lg bg-white dark:bg-gray-900 dark:text-gray-100"
|
|
843
|
+
>
|
|
844
|
+
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-3">
|
|
845
|
+
Select Project Directory
|
|
846
|
+
</h3>
|
|
847
|
+
<nav class="flex items-center gap-1 text-sm mb-3 flex-wrap">
|
|
848
|
+
<button
|
|
849
|
+
:for="crumb in folderPickerBreadcrumbs"
|
|
850
|
+
:key="crumb.path"
|
|
851
|
+
type="button"
|
|
852
|
+
:on:click="folderPickerNavigate(crumb.path)"
|
|
853
|
+
class="text-indigo-600 hover:text-indigo-800 dark:text-indigo-400 dark:hover:text-indigo-300 bg-transparent border-none cursor-pointer p-0 font-medium transition duration-200"
|
|
854
|
+
>
|
|
855
|
+
{{ crumb.name }}<span class="text-gray-400 ml-1">/</span>
|
|
856
|
+
</button>
|
|
857
|
+
</nav>
|
|
858
|
+
<div
|
|
859
|
+
class="border border-gray-200 dark:border-gray-600 rounded-md max-h-64 overflow-auto"
|
|
860
|
+
>
|
|
861
|
+
<button
|
|
862
|
+
:show="folderPickerPath"
|
|
863
|
+
type="button"
|
|
864
|
+
:on:click="folderPickerUp()"
|
|
865
|
+
class="w-full text-left px-3 py-2 text-sm hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors bg-transparent border-none border-b border-gray-200 dark:border-gray-600 cursor-pointer flex items-center gap-2 text-indigo-600 dark:text-indigo-400 font-medium"
|
|
866
|
+
>
|
|
867
|
+
<svg
|
|
868
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
869
|
+
class="h-4 w-4"
|
|
870
|
+
fill="none"
|
|
871
|
+
viewBox="0 0 24 24"
|
|
872
|
+
stroke="currentColor"
|
|
873
|
+
>
|
|
874
|
+
<path
|
|
875
|
+
stroke-linecap="round"
|
|
876
|
+
stroke-linejoin="round"
|
|
877
|
+
stroke-width="2"
|
|
878
|
+
d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"
|
|
879
|
+
/>
|
|
880
|
+
</svg>
|
|
881
|
+
..
|
|
882
|
+
</button>
|
|
883
|
+
<button
|
|
884
|
+
:for="dir in folderPickerDirs"
|
|
885
|
+
:key="dir.path"
|
|
886
|
+
type="button"
|
|
887
|
+
:on:click="folderPickerNavigate(dir.path)"
|
|
888
|
+
class="w-full text-left px-3 py-2 text-sm hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors bg-transparent border-none cursor-pointer flex items-center gap-2 text-gray-700 dark:text-gray-300"
|
|
889
|
+
>
|
|
890
|
+
<svg
|
|
891
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
892
|
+
class="h-4 w-4 text-indigo-400 shrink-0"
|
|
893
|
+
fill="none"
|
|
894
|
+
viewBox="0 0 24 24"
|
|
895
|
+
stroke="currentColor"
|
|
896
|
+
>
|
|
897
|
+
<path
|
|
898
|
+
stroke-linecap="round"
|
|
899
|
+
stroke-linejoin="round"
|
|
900
|
+
stroke-width="2"
|
|
901
|
+
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"
|
|
902
|
+
/>
|
|
903
|
+
</svg>
|
|
904
|
+
{{ dir.name }}
|
|
905
|
+
</button>
|
|
906
|
+
<div
|
|
907
|
+
:show="!folderPickerLoading && folderPickerDirs.length === 0"
|
|
908
|
+
class="px-3 py-4 text-center text-gray-400 text-sm"
|
|
909
|
+
>
|
|
910
|
+
No subdirectories.
|
|
911
|
+
</div>
|
|
912
|
+
</div>
|
|
913
|
+
<div class="mt-4">
|
|
914
|
+
<button
|
|
915
|
+
:if="!showNewFolderInput"
|
|
916
|
+
type="button"
|
|
917
|
+
:on:click="startNewFolder()"
|
|
918
|
+
title="Create new folder"
|
|
919
|
+
class="text-xs text-indigo-600 hover:text-indigo-700 font-medium flex items-center gap-1 bg-transparent border-none cursor-pointer p-0"
|
|
920
|
+
>
|
|
921
|
+
<svg
|
|
922
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
923
|
+
class="h-3 w-3"
|
|
924
|
+
fill="none"
|
|
925
|
+
viewBox="0 0 24 24"
|
|
926
|
+
stroke="currentColor"
|
|
927
|
+
>
|
|
928
|
+
<path
|
|
929
|
+
stroke-linecap="round"
|
|
930
|
+
stroke-linejoin="round"
|
|
931
|
+
stroke-width="2"
|
|
932
|
+
d="M12 4v16m8-8H4"
|
|
933
|
+
/>
|
|
934
|
+
</svg>
|
|
935
|
+
New Folder
|
|
936
|
+
</button>
|
|
937
|
+
<form
|
|
938
|
+
:if="showNewFolderInput"
|
|
939
|
+
class="flex items-center gap-2"
|
|
940
|
+
:on:submit.prevent="createFolder()"
|
|
941
|
+
>
|
|
942
|
+
<input
|
|
943
|
+
type="text"
|
|
944
|
+
:bind="newFolderName"
|
|
945
|
+
placeholder="Folder name"
|
|
946
|
+
class="flex-grow px-2 py-1 text-xs border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded focus:ring-1 focus:ring-indigo-500 focus:outline-none"
|
|
947
|
+
required
|
|
948
|
+
:attr:autofocus="showNewFolderInput"
|
|
949
|
+
/>
|
|
950
|
+
<button
|
|
951
|
+
type="submit"
|
|
952
|
+
class="px-2 py-1 text-xs font-medium text-white bg-indigo-600 rounded hover:bg-indigo-700"
|
|
953
|
+
>
|
|
954
|
+
Create
|
|
955
|
+
</button>
|
|
956
|
+
<button
|
|
957
|
+
type="button"
|
|
958
|
+
:on:click="showNewFolderInput = false"
|
|
959
|
+
class="px-2 py-1 text-xs font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-800 rounded hover:bg-gray-200 dark:hover:bg-gray-700 border-none"
|
|
960
|
+
>
|
|
961
|
+
Cancel
|
|
962
|
+
</button>
|
|
963
|
+
</form>
|
|
964
|
+
</div>
|
|
965
|
+
<div class="mt-4 flex justify-end gap-3">
|
|
966
|
+
<button
|
|
967
|
+
type="button"
|
|
968
|
+
:on:click="closeFolderPicker()"
|
|
969
|
+
class="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 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 transition duration-200 cursor-pointer"
|
|
970
|
+
>
|
|
971
|
+
Cancel
|
|
972
|
+
</button>
|
|
973
|
+
<button
|
|
974
|
+
type="button"
|
|
975
|
+
:on:click="folderPickerSelect(folderPickerPath)"
|
|
976
|
+
class="px-4 py-2 text-sm font-medium text-white bg-indigo-600 border border-transparent rounded-md shadow-sm hover:bg-indigo-700 transition duration-200 cursor-pointer"
|
|
977
|
+
>
|
|
978
|
+
Select This Folder
|
|
979
|
+
</button>
|
|
980
|
+
</div>
|
|
981
|
+
</dialog>
|
|
982
|
+
</body>
|
|
983
|
+
</html>
|