@cat-factory/app 0.46.8 → 0.46.10
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/app/components/board/AddTaskModal.vue +106 -88
- package/app/components/board/AgentFailureCard.vue +8 -5
- package/app/components/board/AgentStopButton.vue +8 -5
- package/app/components/board/BoardCanvas.vue +13 -9
- package/app/components/board/RecurringPipelineModal.vue +33 -23
- package/app/components/board/nodes/BlockNode.vue +50 -34
- package/app/components/board/nodes/DecisionBadge.vue +6 -3
- package/app/components/board/nodes/DraggableTask.vue +2 -1
- package/app/components/board/nodes/EpicNode.vue +10 -3
- package/app/components/board/nodes/ModuleFrame.vue +6 -5
- package/app/components/board/nodes/TaskCard.vue +40 -22
- package/app/components/board/nodes/TaskPipelineMini.vue +4 -3
- package/app/components/layout/BoardToolbar.vue +26 -19
- package/i18n/locales/en.json +204 -0
- package/i18n/locales/es.json +194 -2
- package/i18n/locales/fr.json +194 -2
- package/i18n/locales/pl.json +194 -2
- package/i18n/locales/uk.json +194 -2
- package/package.json +8 -8
|
@@ -26,6 +26,7 @@ const modelPresets = useModelPresetsStore()
|
|
|
26
26
|
const pipelines = usePipelinesStore()
|
|
27
27
|
const agentConfig = useAgentConfigStore()
|
|
28
28
|
const toast = useToast()
|
|
29
|
+
const { t } = useI18n()
|
|
29
30
|
|
|
30
31
|
const { linkPending } = useContextLinking()
|
|
31
32
|
|
|
@@ -52,13 +53,13 @@ const technical = ref(false)
|
|
|
52
53
|
// delegates to <RecurringPipelineModal> instead of creating a one-off task here.
|
|
53
54
|
type TaskTypeChoice = CreateTaskType | 'recurring'
|
|
54
55
|
const taskType = ref<TaskTypeChoice>('feature')
|
|
55
|
-
const TASK_TYPES
|
|
56
|
-
{ value: 'feature', label: '
|
|
57
|
-
{ value: 'bug', label: '
|
|
58
|
-
{ value: 'document', label: '
|
|
59
|
-
{ value: 'spike', label: '
|
|
60
|
-
{ value: 'recurring', label: '
|
|
61
|
-
]
|
|
56
|
+
const TASK_TYPES = computed<{ value: TaskTypeChoice; label: string; icon: string }[]>(() => [
|
|
57
|
+
{ value: 'feature', label: t('board.addTask.types.feature'), icon: 'i-lucide-sparkles' },
|
|
58
|
+
{ value: 'bug', label: t('board.addTask.types.bug'), icon: 'i-lucide-bug' },
|
|
59
|
+
{ value: 'document', label: t('board.addTask.types.document'), icon: 'i-lucide-file-text' },
|
|
60
|
+
{ value: 'spike', label: t('board.addTask.types.spike'), icon: 'i-lucide-flask-conical' },
|
|
61
|
+
{ value: 'recurring', label: t('board.addTask.types.recurring'), icon: 'i-lucide-repeat' },
|
|
62
|
+
])
|
|
62
63
|
const isRecurring = computed(() => taskType.value === 'recurring')
|
|
63
64
|
|
|
64
65
|
// Per-type fields (only the ones relevant to the chosen type are shown / sent).
|
|
@@ -114,12 +115,18 @@ const mergePresetId = ref('')
|
|
|
114
115
|
const modelPresetId = ref('')
|
|
115
116
|
const pipelineId = ref('')
|
|
116
117
|
|
|
118
|
+
const defaultPresetLabel = computed(() =>
|
|
119
|
+
mergePresets.defaultPreset
|
|
120
|
+
? t('board.addTask.defaultPreset', {
|
|
121
|
+
name: mergePresets.defaultPreset.name,
|
|
122
|
+
thresholds: mergePresetThresholds(mergePresets.defaultPreset),
|
|
123
|
+
})
|
|
124
|
+
: t('board.addTask.workspaceDefault'),
|
|
125
|
+
)
|
|
117
126
|
const presetMenu = computed(() => [
|
|
118
127
|
[
|
|
119
128
|
{
|
|
120
|
-
label:
|
|
121
|
-
? `Default (${mergePresets.defaultPreset.name}) — ${mergePresetThresholds(mergePresets.defaultPreset)}`
|
|
122
|
-
: 'Workspace default',
|
|
129
|
+
label: defaultPresetLabel.value,
|
|
123
130
|
icon: 'i-lucide-rotate-ccw',
|
|
124
131
|
onSelect: () => (mergePresetId.value = ''),
|
|
125
132
|
},
|
|
@@ -131,22 +138,21 @@ const presetMenu = computed(() => [
|
|
|
131
138
|
],
|
|
132
139
|
])
|
|
133
140
|
const selectedPresetLabel = computed(() => {
|
|
134
|
-
if (!mergePresetId.value)
|
|
135
|
-
return mergePresets.defaultPreset
|
|
136
|
-
? `Default (${mergePresets.defaultPreset.name}) — ${mergePresetThresholds(mergePresets.defaultPreset)}`
|
|
137
|
-
: 'Workspace default'
|
|
138
|
-
}
|
|
141
|
+
if (!mergePresetId.value) return defaultPresetLabel.value
|
|
139
142
|
const picked = mergePresets.presets.find((p) => p.id === mergePresetId.value)
|
|
140
|
-
return picked ? mergePresetOptionLabel(picked) : '
|
|
143
|
+
return picked ? mergePresetOptionLabel(picked) : t('board.addTask.workspaceDefault')
|
|
141
144
|
})
|
|
142
145
|
|
|
143
146
|
// Model preset: which model each agent runs on. Empty = workspace default preset.
|
|
147
|
+
const defaultModelPresetLabel = computed(() =>
|
|
148
|
+
modelPresets.defaultPreset
|
|
149
|
+
? t('board.addTask.defaultModelPreset', { name: modelPresets.defaultPreset.name })
|
|
150
|
+
: t('board.addTask.workspaceDefault'),
|
|
151
|
+
)
|
|
144
152
|
const modelPresetMenu = computed(() => [
|
|
145
153
|
[
|
|
146
154
|
{
|
|
147
|
-
label:
|
|
148
|
-
? `Default (${modelPresets.defaultPreset.name})`
|
|
149
|
-
: 'Workspace default',
|
|
155
|
+
label: defaultModelPresetLabel.value,
|
|
150
156
|
icon: 'i-lucide-rotate-ccw',
|
|
151
157
|
onSelect: () => (modelPresetId.value = ''),
|
|
152
158
|
},
|
|
@@ -158,18 +164,17 @@ const modelPresetMenu = computed(() => [
|
|
|
158
164
|
],
|
|
159
165
|
])
|
|
160
166
|
const selectedModelPresetLabel = computed(() => {
|
|
161
|
-
if (!modelPresetId.value)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return modelPresets.presets.find((p) => p.id === modelPresetId.value)?.name ?? 'Workspace default'
|
|
167
|
+
if (!modelPresetId.value) return defaultModelPresetLabel.value
|
|
168
|
+
return (
|
|
169
|
+
modelPresets.presets.find((p) => p.id === modelPresetId.value)?.name ??
|
|
170
|
+
t('board.addTask.workspaceDefault')
|
|
171
|
+
)
|
|
167
172
|
})
|
|
168
173
|
|
|
169
174
|
const pipelineMenu = computed(() => [
|
|
170
175
|
[
|
|
171
176
|
{
|
|
172
|
-
label: '
|
|
177
|
+
label: t('board.addTask.chooseAtRunTime'),
|
|
173
178
|
icon: 'i-lucide-rotate-ccw',
|
|
174
179
|
onSelect: () => (pipelineId.value = ''),
|
|
175
180
|
},
|
|
@@ -181,7 +186,7 @@ const pipelineMenu = computed(() => [
|
|
|
181
186
|
],
|
|
182
187
|
])
|
|
183
188
|
const selectedPipelineLabel = computed(
|
|
184
|
-
() => pipelines.getPipeline(pipelineId.value)?.name ?? '
|
|
189
|
+
() => pipelines.getPipeline(pipelineId.value)?.name ?? t('board.addTask.chooseAtRunTime'),
|
|
185
190
|
)
|
|
186
191
|
|
|
187
192
|
// Task-level agent config contributed by the selected pipeline's agents (e.g. the
|
|
@@ -362,7 +367,7 @@ async function add() {
|
|
|
362
367
|
const failed = await linkPending(block.id, pendingContext.value)
|
|
363
368
|
if (failed > 0) {
|
|
364
369
|
toast.add({
|
|
365
|
-
title:
|
|
370
|
+
title: t('board.addTask.linkFailed', { count: failed }, failed),
|
|
366
371
|
icon: 'i-lucide-triangle-alert',
|
|
367
372
|
color: 'warning',
|
|
368
373
|
})
|
|
@@ -371,7 +376,7 @@ async function add() {
|
|
|
371
376
|
ui.closeAddTask()
|
|
372
377
|
} catch (e) {
|
|
373
378
|
toast.add({
|
|
374
|
-
title: '
|
|
379
|
+
title: t('board.addTask.addFailedTitle'),
|
|
375
380
|
description: e instanceof Error ? e.message : String(e),
|
|
376
381
|
icon: 'i-lucide-triangle-alert',
|
|
377
382
|
color: 'error',
|
|
@@ -383,25 +388,29 @@ async function add() {
|
|
|
383
388
|
</script>
|
|
384
389
|
|
|
385
390
|
<template>
|
|
386
|
-
<UModal v-model:open="open" title="
|
|
391
|
+
<UModal v-model:open="open" :title="t('board.addTask.title')">
|
|
387
392
|
<template #body>
|
|
388
393
|
<div class="space-y-4" data-testid="add-task-modal">
|
|
389
394
|
<p v-if="container" class="text-xs text-slate-400">
|
|
390
|
-
|
|
395
|
+
<i18n-t keypath="board.addTask.newTaskIn" tag="span" scope="global">
|
|
396
|
+
<template #container>
|
|
397
|
+
<span class="font-medium text-slate-200">{{ container.title }}</span>
|
|
398
|
+
</template>
|
|
399
|
+
</i18n-t>
|
|
391
400
|
</p>
|
|
392
401
|
|
|
393
|
-
<UFormField label="
|
|
402
|
+
<UFormField :label="t('board.addTask.typeLabel')">
|
|
394
403
|
<div class="flex flex-wrap gap-1">
|
|
395
404
|
<UButton
|
|
396
|
-
v-for="
|
|
397
|
-
:key="
|
|
398
|
-
:color="taskType ===
|
|
399
|
-
:variant="taskType ===
|
|
400
|
-
:icon="
|
|
405
|
+
v-for="ty in TASK_TYPES"
|
|
406
|
+
:key="ty.value"
|
|
407
|
+
:color="taskType === ty.value ? 'primary' : 'neutral'"
|
|
408
|
+
:variant="taskType === ty.value ? 'soft' : 'ghost'"
|
|
409
|
+
:icon="ty.icon"
|
|
401
410
|
size="xs"
|
|
402
|
-
@click="taskType =
|
|
411
|
+
@click="taskType = ty.value"
|
|
403
412
|
>
|
|
404
|
-
{{
|
|
413
|
+
{{ ty.label }}
|
|
405
414
|
</UButton>
|
|
406
415
|
</div>
|
|
407
416
|
</UFormField>
|
|
@@ -412,20 +421,19 @@ async function add() {
|
|
|
412
421
|
class="rounded-lg border border-slate-800 p-3 text-[11px] text-slate-400"
|
|
413
422
|
>
|
|
414
423
|
<template v-if="recurringFrameId">
|
|
415
|
-
|
|
424
|
+
{{ t('board.addTask.recurringWithFrame') }}
|
|
416
425
|
</template>
|
|
417
426
|
<template v-else>
|
|
418
|
-
|
|
419
|
-
one).
|
|
427
|
+
{{ t('board.addTask.recurringNoFrame') }}
|
|
420
428
|
</template>
|
|
421
429
|
</div>
|
|
422
430
|
|
|
423
431
|
<template v-if="!isRecurring">
|
|
424
|
-
<UFormField label="
|
|
432
|
+
<UFormField :label="t('board.addTask.titleField')" required>
|
|
425
433
|
<UInput
|
|
426
434
|
v-model="title"
|
|
427
435
|
data-testid="add-task-title"
|
|
428
|
-
placeholder="
|
|
436
|
+
:placeholder="t('board.addTask.titlePlaceholder')"
|
|
429
437
|
autofocus
|
|
430
438
|
class="w-full"
|
|
431
439
|
@keydown.enter="add"
|
|
@@ -438,7 +446,7 @@ async function add() {
|
|
|
438
446
|
<UFormField
|
|
439
447
|
v-for="issue in linkedIssueBodies"
|
|
440
448
|
:key="issue.key"
|
|
441
|
-
:label="
|
|
449
|
+
:label="t('board.addTask.issueIncluded', { title: issue.title })"
|
|
442
450
|
>
|
|
443
451
|
<UTextarea
|
|
444
452
|
:model-value="issue.body"
|
|
@@ -450,18 +458,24 @@ async function add() {
|
|
|
450
458
|
/>
|
|
451
459
|
</UFormField>
|
|
452
460
|
<p v-if="resolvingIssueBodies" class="text-[11px] text-slate-500">
|
|
453
|
-
|
|
461
|
+
{{ t('board.addTask.loadingIssue') }}
|
|
454
462
|
</p>
|
|
455
463
|
|
|
456
|
-
<UFormField
|
|
464
|
+
<UFormField
|
|
465
|
+
:label="
|
|
466
|
+
hasLinkedIssueBody
|
|
467
|
+
? t('board.addTask.additionalNotes')
|
|
468
|
+
: t('board.addTask.description')
|
|
469
|
+
"
|
|
470
|
+
>
|
|
457
471
|
<UTextarea
|
|
458
472
|
v-model="description"
|
|
459
473
|
:rows="4"
|
|
460
474
|
autoresize
|
|
461
475
|
:placeholder="
|
|
462
476
|
hasLinkedIssueBody
|
|
463
|
-
? '
|
|
464
|
-
: '
|
|
477
|
+
? t('board.addTask.notesPlaceholder')
|
|
478
|
+
: t('board.addTask.descriptionPlaceholder')
|
|
465
479
|
"
|
|
466
480
|
class="w-full"
|
|
467
481
|
/>
|
|
@@ -469,20 +483,18 @@ async function add() {
|
|
|
469
483
|
|
|
470
484
|
<UCheckbox v-model="technical" name="technical">
|
|
471
485
|
<template #label>
|
|
472
|
-
<span class="text-sm text-slate-200">
|
|
486
|
+
<span class="text-sm text-slate-200">{{ t('board.addTask.technical') }}</span>
|
|
473
487
|
</template>
|
|
474
488
|
<template #description>
|
|
475
489
|
<span class="text-[11px] text-slate-500">
|
|
476
|
-
|
|
477
|
-
definition as primary and the spec as a regression reference; leave off to let the
|
|
478
|
-
spec phase decide.
|
|
490
|
+
{{ t('board.addTask.technicalHint') }}
|
|
479
491
|
</span>
|
|
480
492
|
</template>
|
|
481
493
|
</UCheckbox>
|
|
482
494
|
|
|
483
495
|
<!-- Per-type fields. -->
|
|
484
496
|
<div v-if="taskType === 'bug'" class="grid grid-cols-2 gap-3">
|
|
485
|
-
<UFormField label="
|
|
497
|
+
<UFormField :label="t('board.addTask.severity')">
|
|
486
498
|
<div class="flex flex-wrap gap-1">
|
|
487
499
|
<UButton
|
|
488
500
|
v-for="s in SEVERITIES"
|
|
@@ -497,29 +509,29 @@ async function add() {
|
|
|
497
509
|
</UButton>
|
|
498
510
|
</div>
|
|
499
511
|
</UFormField>
|
|
500
|
-
<UFormField label="
|
|
512
|
+
<UFormField :label="t('board.addTask.stepsToReproduce')" class="col-span-2">
|
|
501
513
|
<UTextarea
|
|
502
514
|
v-model="stepsToReproduce"
|
|
503
515
|
:rows="2"
|
|
504
516
|
autoresize
|
|
505
|
-
placeholder="
|
|
517
|
+
:placeholder="t('board.addTask.stepsToReproducePlaceholder')"
|
|
506
518
|
class="w-full"
|
|
507
519
|
/>
|
|
508
520
|
</UFormField>
|
|
509
521
|
</div>
|
|
510
522
|
|
|
511
|
-
<UFormField v-else-if="taskType === 'spike'" label="
|
|
523
|
+
<UFormField v-else-if="taskType === 'spike'" :label="t('board.addTask.timebox')">
|
|
512
524
|
<UInput
|
|
513
525
|
v-model.number="timeboxHours"
|
|
514
526
|
type="number"
|
|
515
527
|
min="0"
|
|
516
|
-
placeholder="
|
|
528
|
+
:placeholder="t('board.addTask.timeboxPlaceholder')"
|
|
517
529
|
class="w-full"
|
|
518
530
|
/>
|
|
519
531
|
</UFormField>
|
|
520
532
|
|
|
521
533
|
<div v-else-if="taskType === 'document'" class="space-y-3">
|
|
522
|
-
<UFormField label="
|
|
534
|
+
<UFormField :label="t('board.addTask.documentKind')">
|
|
523
535
|
<div class="flex flex-wrap gap-1">
|
|
524
536
|
<UButton
|
|
525
537
|
v-for="k in DOC_KINDS"
|
|
@@ -535,33 +547,39 @@ async function add() {
|
|
|
535
547
|
</div>
|
|
536
548
|
</UFormField>
|
|
537
549
|
<div class="grid grid-cols-2 gap-3">
|
|
538
|
-
<UFormField label="
|
|
550
|
+
<UFormField :label="t('board.addTask.audience')" :hint="t('board.addTask.optional')">
|
|
539
551
|
<UInput
|
|
540
552
|
v-model="docAudience"
|
|
541
|
-
placeholder="
|
|
553
|
+
:placeholder="t('board.addTask.audiencePlaceholder')"
|
|
542
554
|
class="w-full"
|
|
543
555
|
/>
|
|
544
556
|
</UFormField>
|
|
545
|
-
<UFormField
|
|
557
|
+
<UFormField
|
|
558
|
+
:label="t('board.addTask.targetPath')"
|
|
559
|
+
:hint="t('board.addTask.optional')"
|
|
560
|
+
>
|
|
546
561
|
<UInput
|
|
547
562
|
v-model="docTargetPath"
|
|
548
|
-
placeholder="
|
|
563
|
+
:placeholder="t('board.addTask.targetPathPlaceholder')"
|
|
549
564
|
class="w-full"
|
|
550
565
|
/>
|
|
551
566
|
</UFormField>
|
|
552
567
|
</div>
|
|
553
|
-
<UFormField
|
|
568
|
+
<UFormField
|
|
569
|
+
:label="t('board.addTask.outlineHints')"
|
|
570
|
+
:hint="t('board.addTask.optional')"
|
|
571
|
+
>
|
|
554
572
|
<UTextarea
|
|
555
573
|
v-model="docOutlineHints"
|
|
556
574
|
:rows="2"
|
|
557
|
-
placeholder="
|
|
575
|
+
:placeholder="t('board.addTask.outlineHintsPlaceholder')"
|
|
558
576
|
class="w-full"
|
|
559
577
|
/>
|
|
560
578
|
</UFormField>
|
|
561
579
|
</div>
|
|
562
580
|
|
|
563
581
|
<div class="grid grid-cols-2 gap-3">
|
|
564
|
-
<UFormField label="
|
|
582
|
+
<UFormField :label="t('board.addTask.pipeline')">
|
|
565
583
|
<UDropdownMenu :items="pipelineMenu" class="w-full">
|
|
566
584
|
<UButton
|
|
567
585
|
color="neutral"
|
|
@@ -576,7 +594,7 @@ async function add() {
|
|
|
576
594
|
</UDropdownMenu>
|
|
577
595
|
</UFormField>
|
|
578
596
|
|
|
579
|
-
<UFormField label="
|
|
597
|
+
<UFormField :label="t('board.addTask.mergePolicy')">
|
|
580
598
|
<UDropdownMenu :items="presetMenu" class="w-full">
|
|
581
599
|
<UButton
|
|
582
600
|
color="neutral"
|
|
@@ -591,7 +609,7 @@ async function add() {
|
|
|
591
609
|
</UDropdownMenu>
|
|
592
610
|
</UFormField>
|
|
593
611
|
|
|
594
|
-
<UFormField label="
|
|
612
|
+
<UFormField :label="t('board.addTask.modelPreset')">
|
|
595
613
|
<UDropdownMenu :items="modelPresetMenu" class="w-full">
|
|
596
614
|
<UButton
|
|
597
615
|
color="neutral"
|
|
@@ -609,7 +627,7 @@ async function add() {
|
|
|
609
627
|
|
|
610
628
|
<div v-if="configDescriptors.length" class="space-y-3">
|
|
611
629
|
<span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
612
|
-
|
|
630
|
+
{{ t('board.addTask.agentConfiguration') }}
|
|
613
631
|
</span>
|
|
614
632
|
<div v-for="d in configDescriptors" :key="d.id" class="space-y-1">
|
|
615
633
|
<div class="text-[11px] text-slate-400">{{ d.label }}</div>
|
|
@@ -633,7 +651,7 @@ async function add() {
|
|
|
633
651
|
<div class="space-y-2">
|
|
634
652
|
<div class="flex items-center justify-between">
|
|
635
653
|
<span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
636
|
-
|
|
654
|
+
{{ t('board.addTask.contextDocuments') }}
|
|
637
655
|
</span>
|
|
638
656
|
<UButton
|
|
639
657
|
v-if="docsConnected"
|
|
@@ -643,7 +661,7 @@ async function add() {
|
|
|
643
661
|
:icon="showDocPicker ? 'i-lucide-x' : 'i-lucide-plus'"
|
|
644
662
|
@click="showDocPicker = !showDocPicker"
|
|
645
663
|
>
|
|
646
|
-
{{ showDocPicker ? '
|
|
664
|
+
{{ showDocPicker ? t('board.addTask.done') : t('board.addTask.attach') }}
|
|
647
665
|
</UButton>
|
|
648
666
|
<UButton
|
|
649
667
|
v-else
|
|
@@ -654,11 +672,11 @@ async function add() {
|
|
|
654
672
|
disabled
|
|
655
673
|
:title="
|
|
656
674
|
documents.available
|
|
657
|
-
? '
|
|
658
|
-
: '
|
|
675
|
+
? t('board.addTask.attachDocDisabledConnect')
|
|
676
|
+
: t('board.addTask.attachDocDisabledEnable')
|
|
659
677
|
"
|
|
660
678
|
>
|
|
661
|
-
|
|
679
|
+
{{ t('board.addTask.attach') }}
|
|
662
680
|
</UButton>
|
|
663
681
|
</div>
|
|
664
682
|
<ContextDocumentPicker
|
|
@@ -684,7 +702,7 @@ async function add() {
|
|
|
684
702
|
size="xs"
|
|
685
703
|
class="ml-1 shrink-0"
|
|
686
704
|
>
|
|
687
|
-
|
|
705
|
+
{{ t('board.addTask.importsOnAdd') }}
|
|
688
706
|
</UBadge>
|
|
689
707
|
<button
|
|
690
708
|
type="button"
|
|
@@ -696,7 +714,7 @@ async function add() {
|
|
|
696
714
|
</div>
|
|
697
715
|
</div>
|
|
698
716
|
<p v-else class="text-[11px] text-slate-500">
|
|
699
|
-
|
|
717
|
+
{{ t('board.addTask.noDocsHint') }}
|
|
700
718
|
</p>
|
|
701
719
|
</div>
|
|
702
720
|
|
|
@@ -704,7 +722,7 @@ async function add() {
|
|
|
704
722
|
<div class="space-y-2">
|
|
705
723
|
<div class="flex items-center justify-between">
|
|
706
724
|
<span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
707
|
-
|
|
725
|
+
{{ t('board.addTask.contextIssues') }}
|
|
708
726
|
</span>
|
|
709
727
|
<UButton
|
|
710
728
|
v-if="issuesConnected"
|
|
@@ -714,7 +732,7 @@ async function add() {
|
|
|
714
732
|
:icon="showIssuePicker ? 'i-lucide-x' : 'i-lucide-plus'"
|
|
715
733
|
@click="showIssuePicker = !showIssuePicker"
|
|
716
734
|
>
|
|
717
|
-
{{ showIssuePicker ? '
|
|
735
|
+
{{ showIssuePicker ? t('board.addTask.done') : t('board.addTask.attach') }}
|
|
718
736
|
</UButton>
|
|
719
737
|
<UButton
|
|
720
738
|
v-else
|
|
@@ -725,11 +743,11 @@ async function add() {
|
|
|
725
743
|
disabled
|
|
726
744
|
:title="
|
|
727
745
|
tasks.available
|
|
728
|
-
? '
|
|
729
|
-
: '
|
|
746
|
+
? t('board.addTask.attachIssueDisabledConnect')
|
|
747
|
+
: t('board.addTask.attachIssueDisabledEnable')
|
|
730
748
|
"
|
|
731
749
|
>
|
|
732
|
-
|
|
750
|
+
{{ t('board.addTask.attach') }}
|
|
733
751
|
</UButton>
|
|
734
752
|
</div>
|
|
735
753
|
<ContextIssuePicker
|
|
@@ -756,7 +774,7 @@ async function add() {
|
|
|
756
774
|
size="xs"
|
|
757
775
|
class="ml-1 shrink-0"
|
|
758
776
|
>
|
|
759
|
-
|
|
777
|
+
{{ t('board.addTask.importsOnAdd') }}
|
|
760
778
|
</UBadge>
|
|
761
779
|
<button
|
|
762
780
|
type="button"
|
|
@@ -768,14 +786,12 @@ async function add() {
|
|
|
768
786
|
</div>
|
|
769
787
|
</div>
|
|
770
788
|
<p v-else class="text-[11px] text-slate-500">
|
|
771
|
-
|
|
772
|
-
this task.
|
|
789
|
+
{{ t('board.addTask.noIssuesHint') }}
|
|
773
790
|
</p>
|
|
774
791
|
</div>
|
|
775
792
|
|
|
776
793
|
<p class="text-[11px] text-slate-500">
|
|
777
|
-
|
|
778
|
-
you can keep editing it until then.
|
|
794
|
+
{{ t('board.addTask.plannedHint') }}
|
|
779
795
|
</p>
|
|
780
796
|
</template>
|
|
781
797
|
</div>
|
|
@@ -783,7 +799,9 @@ async function add() {
|
|
|
783
799
|
|
|
784
800
|
<template #footer>
|
|
785
801
|
<div class="flex w-full justify-end gap-2">
|
|
786
|
-
<UButton color="neutral" variant="ghost" @click="ui.closeAddTask()">
|
|
802
|
+
<UButton color="neutral" variant="ghost" @click="ui.closeAddTask()">{{
|
|
803
|
+
t('common.cancel')
|
|
804
|
+
}}</UButton>
|
|
787
805
|
<UButton
|
|
788
806
|
color="primary"
|
|
789
807
|
data-testid="add-task-submit"
|
|
@@ -792,7 +810,7 @@ async function add() {
|
|
|
792
810
|
:disabled="!canAdd"
|
|
793
811
|
@click="add"
|
|
794
812
|
>
|
|
795
|
-
{{ isRecurring ? '
|
|
813
|
+
{{ isRecurring ? t('board.addTask.continue') : t('board.addTask.submit') }}
|
|
796
814
|
</UButton>
|
|
797
815
|
</div>
|
|
798
816
|
</template>
|
|
@@ -11,6 +11,7 @@ const props = withDefaults(
|
|
|
11
11
|
{ variant: 'expanded' },
|
|
12
12
|
)
|
|
13
13
|
|
|
14
|
+
const { t } = useI18n()
|
|
14
15
|
const agentRuns = useAgentRunsStore()
|
|
15
16
|
|
|
16
17
|
const compact = computed(() => props.variant === 'compact')
|
|
@@ -19,11 +20,13 @@ const title = computed(() => {
|
|
|
19
20
|
// A `dispatch` failure means the container/runner never accepted the job — say so
|
|
20
21
|
// explicitly rather than the generic "Run failed", and show the verbatim provider
|
|
21
22
|
// error in the collapsible detail below.
|
|
22
|
-
if (failure.value?.kind === 'dispatch') return '
|
|
23
|
-
return props.run.kind === 'bootstrap'
|
|
23
|
+
if (failure.value?.kind === 'dispatch') return t('board.failure.containerFailedToStart')
|
|
24
|
+
return props.run.kind === 'bootstrap'
|
|
25
|
+
? t('board.failure.bootstrapFailed')
|
|
26
|
+
: t('board.failure.runFailed')
|
|
24
27
|
})
|
|
25
28
|
const retryLabel = computed(() =>
|
|
26
|
-
props.run.kind === 'bootstrap' ? '
|
|
29
|
+
props.run.kind === 'bootstrap' ? t('board.failure.retryBootstrap') : t('board.failure.retryRun'),
|
|
27
30
|
)
|
|
28
31
|
|
|
29
32
|
const retrying = ref(false)
|
|
@@ -73,7 +76,7 @@ async function retry() {
|
|
|
73
76
|
|
|
74
77
|
<details v-if="!compact && failure?.detail && failure.detail !== failure.message" class="mt-1">
|
|
75
78
|
<summary class="cursor-pointer text-[10px] text-rose-400/60 hover:text-rose-300">
|
|
76
|
-
|
|
79
|
+
{{ t('board.failure.showDetail') }}
|
|
77
80
|
</summary>
|
|
78
81
|
<pre
|
|
79
82
|
class="mt-1 max-h-32 overflow-auto whitespace-pre-wrap rounded bg-rose-950/60 p-1.5 text-[10px] text-rose-200/80"
|
|
@@ -92,7 +95,7 @@ async function retry() {
|
|
|
92
95
|
:name="retrying ? 'i-lucide-loader-circle' : 'i-lucide-rotate-ccw'"
|
|
93
96
|
:class="[compact ? 'h-3 w-3' : 'h-3.5 w-3.5', { 'animate-spin': retrying }]"
|
|
94
97
|
/>
|
|
95
|
-
{{ retrying ? '
|
|
98
|
+
{{ retrying ? t('board.failure.retrying') : compact ? t('common.retry') : retryLabel }}
|
|
96
99
|
</button>
|
|
97
100
|
</div>
|
|
98
101
|
</template>
|
|
@@ -16,27 +16,30 @@ const props = withDefaults(
|
|
|
16
16
|
variant?: 'solid' | 'soft' | 'ghost' | 'subtle' | 'outline'
|
|
17
17
|
label?: string
|
|
18
18
|
}>(),
|
|
19
|
-
{ size: 'xs', variant: 'soft'
|
|
19
|
+
{ size: 'xs', variant: 'soft' },
|
|
20
20
|
)
|
|
21
21
|
|
|
22
|
+
const { t } = useI18n()
|
|
22
23
|
const agentRuns = useAgentRunsStore()
|
|
23
24
|
const toast = useToast()
|
|
24
25
|
const stopping = ref(false)
|
|
25
26
|
|
|
27
|
+
const displayLabel = computed(() => props.label ?? t('board.stop.label'))
|
|
28
|
+
|
|
26
29
|
async function stop() {
|
|
27
30
|
if (stopping.value) return
|
|
28
31
|
stopping.value = true
|
|
29
32
|
try {
|
|
30
33
|
const kind = await agentRuns.stop(props.runId)
|
|
31
34
|
toast.add({
|
|
32
|
-
title: kind === 'bootstrap' ? '
|
|
33
|
-
description: '
|
|
35
|
+
title: kind === 'bootstrap' ? t('board.stop.bootstrapStopped') : t('board.stop.runStopped'),
|
|
36
|
+
description: t('board.stop.stoppedDescription'),
|
|
34
37
|
icon: 'i-lucide-circle-stop',
|
|
35
38
|
color: 'warning',
|
|
36
39
|
})
|
|
37
40
|
} catch (e) {
|
|
38
41
|
toast.add({
|
|
39
|
-
title: '
|
|
42
|
+
title: t('board.stop.stopFailed'),
|
|
40
43
|
description: e instanceof Error ? e.message : String(e),
|
|
41
44
|
color: 'error',
|
|
42
45
|
})
|
|
@@ -56,6 +59,6 @@ async function stop() {
|
|
|
56
59
|
:loading="stopping"
|
|
57
60
|
@click.stop="stop"
|
|
58
61
|
>
|
|
59
|
-
{{
|
|
62
|
+
{{ displayLabel }}
|
|
60
63
|
</UButton>
|
|
61
64
|
</template>
|
|
@@ -19,6 +19,7 @@ const execution = useExecutionStore()
|
|
|
19
19
|
const ui = useUiStore()
|
|
20
20
|
const github = useGitHubStore()
|
|
21
21
|
const toast = useToast()
|
|
22
|
+
const { t } = useI18n()
|
|
22
23
|
|
|
23
24
|
const { onNodeDragStop, onViewportChange, screenToFlowCoordinate } = useVueFlow(BOARD_FLOW_ID)
|
|
24
25
|
const { draggingId } = useBlockDrag()
|
|
@@ -126,8 +127,8 @@ async function onDrop(event: DragEvent) {
|
|
|
126
127
|
ui.select(block.id)
|
|
127
128
|
} catch {
|
|
128
129
|
toast.add({
|
|
129
|
-
title: '
|
|
130
|
-
description: '
|
|
130
|
+
title: t('board.canvas.addBlockFailedTitle'),
|
|
131
|
+
description: t('board.canvas.addBlockFailedBody'),
|
|
131
132
|
color: 'error',
|
|
132
133
|
})
|
|
133
134
|
}
|
|
@@ -143,13 +144,16 @@ async function onDrop(event: DragEvent) {
|
|
|
143
144
|
if (!target || !pipeline) return
|
|
144
145
|
if (target.level !== 'task') {
|
|
145
146
|
toast.add({
|
|
146
|
-
title: '
|
|
147
|
-
description: '
|
|
147
|
+
title: t('board.canvas.dropOntoTaskTitle'),
|
|
148
|
+
description: t('board.canvas.dropOntoTaskBody'),
|
|
148
149
|
})
|
|
149
150
|
return
|
|
150
151
|
}
|
|
151
152
|
if (!board.isRunnable(target.id)) {
|
|
152
|
-
toast.add({
|
|
153
|
+
toast.add({
|
|
154
|
+
title: t('board.canvas.taskBlockedTitle'),
|
|
155
|
+
description: t('board.canvas.taskBlockedBody'),
|
|
156
|
+
})
|
|
153
157
|
return
|
|
154
158
|
}
|
|
155
159
|
execution.start(target.id, pipeline)
|
|
@@ -204,14 +208,14 @@ async function onDrop(event: DragEvent) {
|
|
|
204
208
|
>
|
|
205
209
|
<UIcon name="i-lucide-layout-dashboard" class="h-10 w-10 text-slate-600" />
|
|
206
210
|
<div>
|
|
207
|
-
<h2 class="text-base font-semibold text-slate-300">
|
|
211
|
+
<h2 class="text-base font-semibold text-slate-300">{{ t('board.canvas.emptyTitle') }}</h2>
|
|
208
212
|
<p class="mt-1 max-w-sm text-sm text-slate-500">
|
|
209
|
-
|
|
213
|
+
{{ t('board.canvas.emptyBody') }}
|
|
210
214
|
</p>
|
|
211
215
|
</div>
|
|
212
216
|
<div class="pointer-events-auto flex flex-wrap items-center justify-center gap-2">
|
|
213
217
|
<UButton color="primary" icon="i-lucide-git-branch-plus" @click="ui.openBootstrap()">
|
|
214
|
-
|
|
218
|
+
{{ t('nav.bootstrapRepo') }}
|
|
215
219
|
</UButton>
|
|
216
220
|
<UButton
|
|
217
221
|
v-if="github.available"
|
|
@@ -220,7 +224,7 @@ async function onDrop(event: DragEvent) {
|
|
|
220
224
|
icon="i-lucide-folder-git-2"
|
|
221
225
|
@click="ui.openAddService()"
|
|
222
226
|
>
|
|
223
|
-
|
|
227
|
+
{{ t('nav.addFromRepo') }}
|
|
224
228
|
</UButton>
|
|
225
229
|
</div>
|
|
226
230
|
</div>
|