@cat-factory/app 0.46.0 → 0.46.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/app/components/board/nodes/BlockNode.vue +11 -7
- package/app/components/board/nodes/DraggableTask.vue +5 -2
- package/app/components/board/nodes/ModuleFrame.vue +3 -3
- package/app/components/board/nodes/TaskCard.vue +2 -2
- package/app/components/brainstorm/BrainstormWindow.vue +2 -2
- package/app/components/clarity/ClarityReviewWindow.vue +2 -2
- package/app/components/consensus/ConsensusSessionWindow.vue +2 -2
- package/app/components/followUp/FollowUpWindow.vue +1 -1
- package/app/components/gates/GateResultView.vue +1 -1
- package/app/components/humanTest/HumanTestWindow.vue +1 -1
- package/app/components/layout/LanguageSwitcher.vue +54 -0
- package/app/components/layout/SideBar.vue +5 -1
- package/app/components/layout/TranslationWarningBanner.vue +69 -0
- package/app/components/panels/AgentStepDetail.vue +2 -2
- package/app/components/panels/GenericStructuredResultView.vue +1 -1
- package/app/components/pipeline/PipelineBuilder.vue +10 -7
- package/app/components/requirements/RequirementsReviewWindow.vue +2 -2
- package/app/components/settings/ModelConfigurationPanel.vue +1 -1
- package/app/components/spec/ServiceSpecWindow.vue +2 -2
- package/app/components/testing/TestReportWindow.vue +1 -1
- package/app/components/visualConfirm/VisualConfirmationWindow.vue +1 -1
- package/app/pages/index.vue +2 -0
- package/app/plugins/locale.client.ts +20 -0
- package/app/stores/locale.ts +20 -0
- package/i18n/locales/en.json +9 -0
- package/i18n/locales/es.json +9 -0
- package/i18n/locales/fr.json +9 -0
- package/i18n/locales/pl.json +9 -0
- package/i18n/locales/uk.json +9 -0
- package/package.json +1 -1
|
@@ -9,6 +9,7 @@ import AgentStopButton from '~/components/board/AgentStopButton.vue'
|
|
|
9
9
|
import { useBlockDrag } from '~/composables/useBlockDrag'
|
|
10
10
|
import { useFrameResize } from '~/composables/useFrameResize'
|
|
11
11
|
import { useFrameStacking } from '~/composables/useFrameStacking'
|
|
12
|
+
import { useViewport } from '~/composables/useViewport'
|
|
12
13
|
|
|
13
14
|
// Vue Flow passes the node's `id` and `data` as props to custom node components.
|
|
14
15
|
// Only frames are rendered as board nodes; their tasks live inside the card.
|
|
@@ -22,6 +23,9 @@ const agentRuns = useAgentRunsStore()
|
|
|
22
23
|
const services = useServicesStore()
|
|
23
24
|
const reviews = useReviewStage()
|
|
24
25
|
const { lod } = useSemanticZoom()
|
|
26
|
+
// Coarse-pointer (touch) bumps the frame-header actions from `xs` to `sm` so
|
|
27
|
+
// they clear a comfortable tap target on phones without affecting mouse desktops.
|
|
28
|
+
const { isTouch } = useViewport()
|
|
25
29
|
|
|
26
30
|
const block = computed<Block | undefined>(() => board.getBlock(props.id))
|
|
27
31
|
/** This service frame is mounted on more than one board in the org. */
|
|
@@ -412,7 +416,7 @@ const ITEM_ICON: Record<string, string> = {
|
|
|
412
416
|
<UButton
|
|
413
417
|
class="nodrag"
|
|
414
418
|
data-testid="frame-add-task"
|
|
415
|
-
size="xs"
|
|
419
|
+
:size="isTouch ? 'sm' : 'xs'"
|
|
416
420
|
variant="ghost"
|
|
417
421
|
color="neutral"
|
|
418
422
|
icon="i-lucide-plus"
|
|
@@ -422,7 +426,7 @@ const ITEM_ICON: Record<string, string> = {
|
|
|
422
426
|
<UButton
|
|
423
427
|
v-if="tasks.anyOffered"
|
|
424
428
|
class="nodrag"
|
|
425
|
-
size="xs"
|
|
429
|
+
:size="isTouch ? 'sm' : 'xs'"
|
|
426
430
|
variant="ghost"
|
|
427
431
|
color="neutral"
|
|
428
432
|
icon="i-lucide-ticket"
|
|
@@ -431,7 +435,7 @@ const ITEM_ICON: Record<string, string> = {
|
|
|
431
435
|
/>
|
|
432
436
|
<UButton
|
|
433
437
|
class="nodrag"
|
|
434
|
-
size="xs"
|
|
438
|
+
:size="isTouch ? 'sm' : 'xs'"
|
|
435
439
|
variant="ghost"
|
|
436
440
|
color="neutral"
|
|
437
441
|
icon="i-lucide-repeat"
|
|
@@ -440,7 +444,7 @@ const ITEM_ICON: Record<string, string> = {
|
|
|
440
444
|
/>
|
|
441
445
|
<UButton
|
|
442
446
|
class="nodrag"
|
|
443
|
-
size="xs"
|
|
447
|
+
:size="isTouch ? 'sm' : 'xs'"
|
|
444
448
|
variant="ghost"
|
|
445
449
|
color="neutral"
|
|
446
450
|
icon="i-lucide-chevron-up"
|
|
@@ -481,17 +485,17 @@ const ITEM_ICON: Record<string, string> = {
|
|
|
481
485
|
`nopan` (alongside `nodrag`) so the pane doesn't pan while resizing —
|
|
482
486
|
same reason as the header handle above. -->
|
|
483
487
|
<div
|
|
484
|
-
class="nodrag nopan absolute right-0 top-0 h-full w-2 cursor-ew-resize hover:bg-sky-400/20"
|
|
488
|
+
class="nodrag nopan absolute right-0 top-0 h-full w-2 cursor-ew-resize hover:bg-sky-400/20 pointer-coarse:w-4"
|
|
485
489
|
title="Drag to resize"
|
|
486
490
|
@pointerdown="onResize($event, 'e')"
|
|
487
491
|
/>
|
|
488
492
|
<div
|
|
489
|
-
class="nodrag nopan absolute bottom-0 left-0 h-2 w-full cursor-ns-resize hover:bg-sky-400/20"
|
|
493
|
+
class="nodrag nopan absolute bottom-0 left-0 h-2 w-full cursor-ns-resize hover:bg-sky-400/20 pointer-coarse:h-4"
|
|
490
494
|
title="Drag to resize"
|
|
491
495
|
@pointerdown="onResize($event, 's')"
|
|
492
496
|
/>
|
|
493
497
|
<div
|
|
494
|
-
class="nodrag nopan absolute bottom-0 right-0 h-4 w-4 cursor-nwse-resize"
|
|
498
|
+
class="nodrag nopan absolute bottom-0 right-0 h-4 w-4 cursor-nwse-resize pointer-coarse:h-11 pointer-coarse:w-11"
|
|
495
499
|
title="Drag to resize"
|
|
496
500
|
@pointerdown="onResize($event, 'se')"
|
|
497
501
|
>
|
|
@@ -41,11 +41,14 @@ function onHandle(e: PointerEvent) {
|
|
|
41
41
|
>
|
|
42
42
|
<!-- drag handle (`nopan` so the pane doesn't pan on a left-drag from here) -->
|
|
43
43
|
<div
|
|
44
|
-
class="nodrag nopan flex cursor-grab items-center justify-center rounded-t-lg border border-b-0 border-slate-700 bg-slate-800/80 py-px active:cursor-grabbing"
|
|
44
|
+
class="nodrag nopan flex cursor-grab items-center justify-center rounded-t-lg border border-b-0 border-slate-700 bg-slate-800/80 py-px active:cursor-grabbing pointer-coarse:py-2"
|
|
45
45
|
title="Drag task"
|
|
46
46
|
@pointerdown="onHandle"
|
|
47
47
|
>
|
|
48
|
-
<UIcon
|
|
48
|
+
<UIcon
|
|
49
|
+
name="i-lucide-grip-horizontal"
|
|
50
|
+
class="h-3 w-3 text-slate-500 pointer-coarse:h-5 pointer-coarse:w-5"
|
|
51
|
+
/>
|
|
49
52
|
</div>
|
|
50
53
|
<TaskCard :task-id="taskId" class="!rounded-t-none" />
|
|
51
54
|
</div>
|
|
@@ -76,17 +76,17 @@ function onResize(e: PointerEvent, edge: 'e' | 's' | 'se') {
|
|
|
76
76
|
<!-- resize handles (drag the borders to resize the module, Miro-style).
|
|
77
77
|
`nopan` (with `nodrag`) so resizing doesn't pan the pane. -->
|
|
78
78
|
<div
|
|
79
|
-
class="nodrag nopan absolute right-0 top-0 h-full w-2 cursor-ew-resize hover:bg-violet-400/20"
|
|
79
|
+
class="nodrag nopan absolute right-0 top-0 h-full w-2 cursor-ew-resize hover:bg-violet-400/20 pointer-coarse:w-4"
|
|
80
80
|
title="Drag to resize"
|
|
81
81
|
@pointerdown="onResize($event, 'e')"
|
|
82
82
|
/>
|
|
83
83
|
<div
|
|
84
|
-
class="nodrag nopan absolute bottom-0 left-0 h-2 w-full cursor-ns-resize hover:bg-violet-400/20"
|
|
84
|
+
class="nodrag nopan absolute bottom-0 left-0 h-2 w-full cursor-ns-resize hover:bg-violet-400/20 pointer-coarse:h-4"
|
|
85
85
|
title="Drag to resize"
|
|
86
86
|
@pointerdown="onResize($event, 's')"
|
|
87
87
|
/>
|
|
88
88
|
<div
|
|
89
|
-
class="nodrag nopan absolute bottom-0 right-0 h-4 w-4 cursor-nwse-resize"
|
|
89
|
+
class="nodrag nopan absolute bottom-0 right-0 h-4 w-4 cursor-nwse-resize pointer-coarse:h-11 pointer-coarse:w-11"
|
|
90
90
|
title="Drag to resize"
|
|
91
91
|
@pointerdown="onResize($event, 'se')"
|
|
92
92
|
>
|
|
@@ -210,12 +210,12 @@ function selectTask() {
|
|
|
210
210
|
<!-- drag-to-connect handle: drag onto another task to make it depend on this one -->
|
|
211
211
|
<button
|
|
212
212
|
type="button"
|
|
213
|
-
class="nodrag shrink-0 cursor-crosshair rounded-full p-0.5 text-slate-500 hover:bg-slate-800 hover:text-amber-400"
|
|
213
|
+
class="nodrag shrink-0 cursor-crosshair rounded-full p-0.5 text-slate-500 hover:bg-slate-800 hover:text-amber-400 pointer-coarse:p-2.5"
|
|
214
214
|
title="Drag onto another task to make it depend on this one"
|
|
215
215
|
@pointerdown.stop="startConnect(task.id, $event)"
|
|
216
216
|
@click.stop
|
|
217
217
|
>
|
|
218
|
-
<UIcon name="i-lucide-spline" class="h-3 w-3" />
|
|
218
|
+
<UIcon name="i-lucide-spline" class="h-3 w-3 pointer-coarse:h-5 pointer-coarse:w-5" />
|
|
219
219
|
</button>
|
|
220
220
|
</div>
|
|
221
221
|
|
|
@@ -220,11 +220,11 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
220
220
|
<Teleport to="body">
|
|
221
221
|
<div
|
|
222
222
|
v-if="open"
|
|
223
|
-
class="fixed inset-0 z-50 flex items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
|
|
223
|
+
class="fixed inset-0 z-50 flex max-h-[100dvh] items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
|
|
224
224
|
@click.self="close"
|
|
225
225
|
>
|
|
226
226
|
<div
|
|
227
|
-
class="flex h-[
|
|
227
|
+
class="flex max-h-[90dvh] w-full max-w-5xl flex-col overflow-hidden rounded-2xl border border-slate-800 bg-slate-900 shadow-2xl"
|
|
228
228
|
>
|
|
229
229
|
<!-- header -->
|
|
230
230
|
<header class="flex items-center gap-3 border-b border-slate-800 px-6 py-4">
|
|
@@ -217,11 +217,11 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
217
217
|
<Teleport to="body">
|
|
218
218
|
<div
|
|
219
219
|
v-if="open"
|
|
220
|
-
class="fixed inset-0 z-50 flex items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
|
|
220
|
+
class="fixed inset-0 z-50 flex max-h-[100dvh] items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
|
|
221
221
|
@click.self="close"
|
|
222
222
|
>
|
|
223
223
|
<div
|
|
224
|
-
class="flex h-[
|
|
224
|
+
class="flex max-h-[90dvh] w-full max-w-5xl flex-col overflow-hidden rounded-2xl border border-slate-800 bg-slate-900 shadow-2xl"
|
|
225
225
|
>
|
|
226
226
|
<!-- header -->
|
|
227
227
|
<header class="flex items-center gap-3 border-b border-slate-800 px-6 py-4">
|
|
@@ -67,11 +67,11 @@ function topScore(c: ConsensusContribution): { label: string; value: number } |
|
|
|
67
67
|
<Teleport to="body">
|
|
68
68
|
<div
|
|
69
69
|
v-if="open"
|
|
70
|
-
class="fixed inset-0 z-50 flex items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
|
|
70
|
+
class="fixed inset-0 z-50 flex max-h-[100dvh] items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
|
|
71
71
|
@click.self="close"
|
|
72
72
|
>
|
|
73
73
|
<div
|
|
74
|
-
class="flex h-[
|
|
74
|
+
class="flex max-h-[90dvh] w-full max-w-5xl flex-col overflow-hidden rounded-2xl border border-slate-800 bg-slate-900 shadow-2xl"
|
|
75
75
|
>
|
|
76
76
|
<!-- header -->
|
|
77
77
|
<header class="flex items-center gap-3 border-b border-slate-800 px-6 py-4">
|
|
@@ -73,7 +73,7 @@ const STATUS_META: Record<
|
|
|
73
73
|
<Teleport to="body">
|
|
74
74
|
<div
|
|
75
75
|
v-if="open"
|
|
76
|
-
class="fixed inset-0 z-50 flex items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
|
|
76
|
+
class="fixed inset-0 z-50 flex max-h-[100dvh] items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
|
|
77
77
|
@click.self="close"
|
|
78
78
|
>
|
|
79
79
|
<div
|
|
@@ -140,7 +140,7 @@ const conflictVerdict = computed(() => {
|
|
|
140
140
|
<Teleport to="body">
|
|
141
141
|
<div
|
|
142
142
|
v-if="open"
|
|
143
|
-
class="fixed inset-0 z-50 flex items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
|
|
143
|
+
class="fixed inset-0 z-50 flex max-h-[100dvh] items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
|
|
144
144
|
@click.self="close"
|
|
145
145
|
>
|
|
146
146
|
<div
|
|
@@ -100,7 +100,7 @@ const canDestroy = computed(
|
|
|
100
100
|
<Teleport to="body">
|
|
101
101
|
<div
|
|
102
102
|
v-if="open"
|
|
103
|
-
class="fixed inset-0 z-50 flex items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
|
|
103
|
+
class="fixed inset-0 z-50 flex max-h-[100dvh] items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
|
|
104
104
|
@click.self="close"
|
|
105
105
|
>
|
|
106
106
|
<div
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { DropdownMenuItem } from '@nuxt/ui'
|
|
3
|
+
import { computed } from 'vue'
|
|
4
|
+
import { useLocaleStore } from '~/stores/locale'
|
|
5
|
+
|
|
6
|
+
// Language picker for the SPA's supported locales, shown at the sidebar bottom next to
|
|
7
|
+
// the user menu. The list is data-driven from the i18n config (`useI18n().locales`), so
|
|
8
|
+
// adding a locale in nuxt.config.ts surfaces it here automatically. Selecting one switches
|
|
9
|
+
// the live locale AND persists the choice (the locale store) so it survives a reload.
|
|
10
|
+
const { t, locale, locales, setLocale } = useI18n()
|
|
11
|
+
const localeStore = useLocaleStore()
|
|
12
|
+
|
|
13
|
+
const current = computed(
|
|
14
|
+
() => locales.value.find((l) => l.code === locale.value)?.name ?? locale.value,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
// The typed-messages guard narrows the locale to the configured union, so take the same
|
|
18
|
+
// type setLocale expects rather than a bare string.
|
|
19
|
+
async function choose(code: typeof locale.value) {
|
|
20
|
+
if (code === locale.value) return
|
|
21
|
+
await setLocale(code)
|
|
22
|
+
localeStore.set(code)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const items = computed<DropdownMenuItem[][]>(() => [
|
|
26
|
+
locales.value.map((l) => ({
|
|
27
|
+
label: l.name ?? l.code,
|
|
28
|
+
icon: l.code === locale.value ? 'i-lucide-check' : undefined,
|
|
29
|
+
onSelect: () => {
|
|
30
|
+
void choose(l.code)
|
|
31
|
+
},
|
|
32
|
+
})),
|
|
33
|
+
])
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<template>
|
|
37
|
+
<UDropdownMenu :items="items" :content="{ side: 'top', align: 'start' }">
|
|
38
|
+
<button
|
|
39
|
+
type="button"
|
|
40
|
+
data-testid="language-switcher"
|
|
41
|
+
:aria-label="t('language.switcher')"
|
|
42
|
+
class="flex w-full items-center gap-2 rounded-lg border border-slate-800 bg-slate-900/60 p-2 text-left transition hover:bg-slate-800/60"
|
|
43
|
+
>
|
|
44
|
+
<UIcon name="i-lucide-languages" class="h-4 w-4 shrink-0 text-slate-400" />
|
|
45
|
+
<div class="min-w-0 flex-1">
|
|
46
|
+
<div class="truncate text-[10px] uppercase tracking-wide text-slate-500">
|
|
47
|
+
{{ t('language.switcher') }}
|
|
48
|
+
</div>
|
|
49
|
+
<div class="truncate text-xs font-medium text-white">{{ current }}</div>
|
|
50
|
+
</div>
|
|
51
|
+
<UIcon name="i-lucide-chevron-up" class="h-4 w-4 shrink-0 text-slate-500" />
|
|
52
|
+
</button>
|
|
53
|
+
</UDropdownMenu>
|
|
54
|
+
</template>
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// default models).
|
|
8
8
|
import { useEventListener, useScrollLock } from '@vueuse/core'
|
|
9
9
|
import BoardSwitcher from '~/components/layout/BoardSwitcher.vue'
|
|
10
|
+
import LanguageSwitcher from '~/components/layout/LanguageSwitcher.vue'
|
|
10
11
|
import UserMenu from '~/components/auth/UserMenu.vue'
|
|
11
12
|
import { useViewport } from '~/composables/useViewport'
|
|
12
13
|
|
|
@@ -306,6 +307,9 @@ watch(
|
|
|
306
307
|
</section>
|
|
307
308
|
</div>
|
|
308
309
|
|
|
309
|
-
<
|
|
310
|
+
<div class="mt-auto space-y-2">
|
|
311
|
+
<LanguageSwitcher />
|
|
312
|
+
<UserMenu />
|
|
313
|
+
</div>
|
|
310
314
|
</aside>
|
|
311
315
|
</template>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref, watch } from 'vue'
|
|
3
|
+
|
|
4
|
+
// Shown whenever the active locale is NOT English: the non-English catalogs are
|
|
5
|
+
// community/AI-provided and may be inaccurate, so warn the user and point them at the
|
|
6
|
+
// repository to report mistakes or open a fix PR. Rendered as a slim full-width strip at
|
|
7
|
+
// the very top (distinct from the centered config-warning cards below it, so they don't
|
|
8
|
+
// overlap). Dismissible per session; re-appears the next time the user switches locale.
|
|
9
|
+
const REPO_URL = 'https://github.com/kibertoad/cat-factory'
|
|
10
|
+
|
|
11
|
+
const { t, locale } = useI18n()
|
|
12
|
+
|
|
13
|
+
const dismissed = ref(false)
|
|
14
|
+
const show = computed(() => locale.value !== 'en' && !dismissed.value)
|
|
15
|
+
|
|
16
|
+
// A fresh switch is a new context for the warning, so un-dismiss on every locale change.
|
|
17
|
+
watch(locale, () => {
|
|
18
|
+
dismissed.value = false
|
|
19
|
+
})
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<Transition name="fade">
|
|
24
|
+
<div
|
|
25
|
+
v-if="show"
|
|
26
|
+
data-testid="translation-warning"
|
|
27
|
+
role="alert"
|
|
28
|
+
class="fixed inset-x-0 top-0 z-50 flex items-center gap-3 border-b border-amber-500/40 bg-amber-950/95 px-4 py-2 text-[13px] text-amber-100 shadow-lg backdrop-blur"
|
|
29
|
+
>
|
|
30
|
+
<UIcon name="i-lucide-languages" class="h-4 w-4 shrink-0 text-amber-400" />
|
|
31
|
+
<p class="min-w-0 flex-1">
|
|
32
|
+
<span class="font-semibold">{{ t('language.warning.title') }}</span>
|
|
33
|
+
<span class="mx-1.5 text-amber-400/60">·</span>
|
|
34
|
+
<i18n-t keypath="language.warning.body" tag="span" scope="global">
|
|
35
|
+
<template #repoLink>
|
|
36
|
+
<a
|
|
37
|
+
:href="REPO_URL"
|
|
38
|
+
target="_blank"
|
|
39
|
+
rel="noopener noreferrer"
|
|
40
|
+
class="inline-flex items-center gap-1 font-medium text-sky-300 hover:underline"
|
|
41
|
+
>
|
|
42
|
+
{{ t('language.warning.repoLinkLabel') }}
|
|
43
|
+
<UIcon name="i-lucide-external-link" class="h-3 w-3" />
|
|
44
|
+
</a>
|
|
45
|
+
</template>
|
|
46
|
+
</i18n-t>
|
|
47
|
+
</p>
|
|
48
|
+
<UButton
|
|
49
|
+
color="neutral"
|
|
50
|
+
variant="ghost"
|
|
51
|
+
size="xs"
|
|
52
|
+
icon="i-lucide-x"
|
|
53
|
+
:aria-label="t('language.warning.dismiss')"
|
|
54
|
+
@click="dismissed = true"
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
</Transition>
|
|
58
|
+
</template>
|
|
59
|
+
|
|
60
|
+
<style scoped>
|
|
61
|
+
.fade-enter-active,
|
|
62
|
+
.fade-leave-active {
|
|
63
|
+
transition: opacity 0.2s ease;
|
|
64
|
+
}
|
|
65
|
+
.fade-enter-from,
|
|
66
|
+
.fade-leave-to {
|
|
67
|
+
opacity: 0;
|
|
68
|
+
}
|
|
69
|
+
</style>
|
|
@@ -176,7 +176,7 @@ async function copyOutput() {
|
|
|
176
176
|
<div
|
|
177
177
|
v-if="open && step && agent"
|
|
178
178
|
data-testid="step-detail"
|
|
179
|
-
class="fixed inset-0 z-50 flex bg-slate-950/96 backdrop-blur-sm"
|
|
179
|
+
class="fixed inset-0 z-50 flex max-h-[100dvh] bg-slate-950/96 backdrop-blur-sm"
|
|
180
180
|
role="dialog"
|
|
181
181
|
aria-modal="true"
|
|
182
182
|
>
|
|
@@ -430,7 +430,7 @@ async function copyOutput() {
|
|
|
430
430
|
bottom sheet (still reachable) below lg, so the gate is always actionable. -->
|
|
431
431
|
<aside
|
|
432
432
|
v-if="approvalPending && !companionExceeded"
|
|
433
|
-
class="absolute inset-x-0 bottom-0 z-10 flex max-h-[
|
|
433
|
+
class="absolute inset-x-0 bottom-0 z-10 flex max-h-[70dvh] flex-col rounded-t-2xl border-t border-slate-700 bg-slate-900/95 shadow-2xl backdrop-blur lg:static lg:inset-auto lg:z-auto lg:max-h-none lg:w-96 lg:shrink-0 lg:rounded-none lg:border-l lg:border-t-0 lg:border-slate-800 lg:bg-slate-900/60 lg:shadow-none lg:backdrop-blur-none"
|
|
434
434
|
>
|
|
435
435
|
<div class="border-b border-slate-800 px-4 py-3">
|
|
436
436
|
<div class="text-[11px] font-semibold uppercase tracking-wide text-amber-400">
|
|
@@ -44,7 +44,7 @@ const customJson = computed<string | null>(() => {
|
|
|
44
44
|
<Teleport to="body">
|
|
45
45
|
<div
|
|
46
46
|
v-if="open"
|
|
47
|
-
class="fixed inset-0 z-50 flex items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
|
|
47
|
+
class="fixed inset-0 z-50 flex max-h-[100dvh] items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
|
|
48
48
|
@click.self="close"
|
|
49
49
|
>
|
|
50
50
|
<div
|
|
@@ -210,9 +210,12 @@ async function clone(p: Pipeline) {
|
|
|
210
210
|
:ui="{ content: 'max-w-[90vw] sm:max-w-2xl lg:max-w-5xl xl:max-w-6xl' }"
|
|
211
211
|
>
|
|
212
212
|
<template #body>
|
|
213
|
-
|
|
213
|
+
<!-- On compact viewports the three sections stack and the slideover body
|
|
214
|
+
scrolls as one column; on lg they become three independently-scrolling
|
|
215
|
+
columns filling the full height. -->
|
|
216
|
+
<div class="grid grid-cols-1 gap-4 lg:h-full lg:grid-cols-3">
|
|
214
217
|
<!-- agent palette -->
|
|
215
|
-
<div class="flex min-h-0
|
|
218
|
+
<div class="flex flex-col lg:min-h-0 lg:overflow-hidden">
|
|
216
219
|
<div class="mb-2 flex shrink-0 items-center justify-between gap-2">
|
|
217
220
|
<h3 class="text-xs font-semibold uppercase tracking-wide text-slate-400">
|
|
218
221
|
Agent palette
|
|
@@ -227,13 +230,13 @@ async function clone(p: Pipeline) {
|
|
|
227
230
|
Add agent
|
|
228
231
|
</UButton>
|
|
229
232
|
</div>
|
|
230
|
-
<div class="min-h-0
|
|
233
|
+
<div class="flex-1 pr-1 lg:min-h-0 lg:overflow-y-auto">
|
|
231
234
|
<AgentPalette @add="add" />
|
|
232
235
|
</div>
|
|
233
236
|
</div>
|
|
234
237
|
|
|
235
238
|
<!-- draft chain -->
|
|
236
|
-
<div class="flex min-h-0
|
|
239
|
+
<div class="flex flex-col lg:min-h-0 lg:overflow-hidden">
|
|
237
240
|
<div class="mb-2 flex items-center justify-between gap-2">
|
|
238
241
|
<h3 class="text-xs font-semibold uppercase tracking-wide text-slate-400">Pipeline</h3>
|
|
239
242
|
<UButton
|
|
@@ -293,7 +296,7 @@ async function clone(p: Pipeline) {
|
|
|
293
296
|
Click agents on the left to assemble a linear pipeline.
|
|
294
297
|
</div>
|
|
295
298
|
|
|
296
|
-
<ol v-else class="
|
|
299
|
+
<ol v-else class="flex-1 space-y-2 pr-1 lg:min-h-0 lg:overflow-y-auto">
|
|
297
300
|
<li
|
|
298
301
|
v-for="(unit, vi) in pipelines.units"
|
|
299
302
|
:key="unit.index"
|
|
@@ -608,7 +611,7 @@ async function clone(p: Pipeline) {
|
|
|
608
611
|
|
|
609
612
|
<!-- Saved pipelines: review the library + delete (the run affordance
|
|
610
613
|
moved to the task card / inspector when the palettes were removed). -->
|
|
611
|
-
<div v-if="pipelines.pipelines.length" class="flex min-h-0
|
|
614
|
+
<div v-if="pipelines.pipelines.length" class="flex flex-col lg:min-h-0 lg:overflow-hidden">
|
|
612
615
|
<div class="mb-2 flex shrink-0 items-center justify-between gap-2">
|
|
613
616
|
<h3 class="text-xs font-semibold uppercase tracking-wide text-slate-400">
|
|
614
617
|
Saved pipelines
|
|
@@ -649,7 +652,7 @@ async function clone(p: Pipeline) {
|
|
|
649
652
|
</UBadge>
|
|
650
653
|
</div>
|
|
651
654
|
|
|
652
|
-
<ul class="
|
|
655
|
+
<ul class="flex-1 space-y-1.5 pr-1 lg:min-h-0 lg:overflow-y-auto">
|
|
653
656
|
<li
|
|
654
657
|
v-for="p in visiblePipelines"
|
|
655
658
|
:key="p.id"
|
|
@@ -386,11 +386,11 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
386
386
|
<Teleport to="body">
|
|
387
387
|
<div
|
|
388
388
|
v-if="open"
|
|
389
|
-
class="fixed inset-0 z-50 flex items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
|
|
389
|
+
class="fixed inset-0 z-50 flex max-h-[100dvh] items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
|
|
390
390
|
@click.self="close"
|
|
391
391
|
>
|
|
392
392
|
<div
|
|
393
|
-
class="flex h-[
|
|
393
|
+
class="flex max-h-[90dvh] w-full max-w-5xl flex-col overflow-hidden rounded-2xl border border-slate-800 bg-slate-900 shadow-2xl"
|
|
394
394
|
>
|
|
395
395
|
<!-- header -->
|
|
396
396
|
<header class="flex items-center gap-3 border-b border-slate-800 px-6 py-4">
|
|
@@ -240,7 +240,7 @@ function fail(title: string, e: unknown) {
|
|
|
240
240
|
<Transition name="reader-fade">
|
|
241
241
|
<div
|
|
242
242
|
v-if="open"
|
|
243
|
-
class="fixed inset-0 z-50 flex flex-col bg-slate-950/96 backdrop-blur-sm"
|
|
243
|
+
class="fixed inset-0 z-50 flex max-h-[100dvh] flex-col bg-slate-950/96 backdrop-blur-sm"
|
|
244
244
|
role="dialog"
|
|
245
245
|
aria-modal="true"
|
|
246
246
|
>
|
|
@@ -104,11 +104,11 @@ function priorityMeta(item: RequirementItem) {
|
|
|
104
104
|
<Teleport to="body">
|
|
105
105
|
<div
|
|
106
106
|
v-if="open"
|
|
107
|
-
class="fixed inset-0 z-50 flex items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
|
|
107
|
+
class="fixed inset-0 z-50 flex max-h-[100dvh] items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
|
|
108
108
|
@click.self="close"
|
|
109
109
|
>
|
|
110
110
|
<div
|
|
111
|
-
class="flex h-[
|
|
111
|
+
class="flex max-h-[90dvh] w-full max-w-5xl flex-col overflow-hidden rounded-2xl border border-slate-800 bg-slate-900 shadow-2xl"
|
|
112
112
|
>
|
|
113
113
|
<!-- header -->
|
|
114
114
|
<header class="flex items-center gap-3 border-b border-slate-800 px-6 py-4">
|
|
@@ -230,7 +230,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
|
|
|
230
230
|
<Teleport to="body">
|
|
231
231
|
<div
|
|
232
232
|
v-if="open"
|
|
233
|
-
class="fixed inset-0 z-50 flex items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
|
|
233
|
+
class="fixed inset-0 z-50 flex max-h-[100dvh] items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
|
|
234
234
|
@click.self="close"
|
|
235
235
|
>
|
|
236
236
|
<div
|
|
@@ -181,7 +181,7 @@ const canApprove = computed(
|
|
|
181
181
|
<Teleport to="body">
|
|
182
182
|
<div
|
|
183
183
|
v-if="open"
|
|
184
|
-
class="fixed inset-0 z-50 flex items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
|
|
184
|
+
class="fixed inset-0 z-50 flex max-h-[100dvh] items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
|
|
185
185
|
@click.self="close"
|
|
186
186
|
>
|
|
187
187
|
<div
|
package/app/pages/index.vue
CHANGED
|
@@ -208,6 +208,8 @@ watch(
|
|
|
208
208
|
|
|
209
209
|
<template>
|
|
210
210
|
<div class="flex h-screen w-screen overflow-hidden bg-slate-950 text-slate-100">
|
|
211
|
+
<!-- Non-English locale warning (unofficial translation); slim strip above everything. -->
|
|
212
|
+
<TranslationWarningBanner />
|
|
211
213
|
<!-- Local-mode setup prompt (missing GitHub PAT); floats over whatever is shown below. -->
|
|
212
214
|
<GitHubPatBanner />
|
|
213
215
|
<!-- AI-readiness prompt (no usable model source, or default preset uses unavailable models). -->
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useLocaleStore } from '~/stores/locale'
|
|
2
|
+
|
|
3
|
+
// Restore the user's persisted language choice on boot. The app defaults to English
|
|
4
|
+
// (the locale store's default); this only re-applies an EXPLICIT prior pick. Kept
|
|
5
|
+
// client-only (the SPA renders client-side) and guarded against an unknown code so a
|
|
6
|
+
// stale/removed locale falls back to the i18n default instead of throwing.
|
|
7
|
+
export default defineNuxtPlugin(async (nuxtApp) => {
|
|
8
|
+
const i18n = nuxtApp.$i18n as {
|
|
9
|
+
locale: { value: string }
|
|
10
|
+
locales: { value: Array<{ code: string }> }
|
|
11
|
+
setLocale: (code: string) => Promise<void>
|
|
12
|
+
}
|
|
13
|
+
if (!i18n?.setLocale) return
|
|
14
|
+
|
|
15
|
+
const stored = useLocaleStore().current
|
|
16
|
+
const supported = i18n.locales.value.some((l) => l.code === stored)
|
|
17
|
+
if (supported && stored !== i18n.locale.value) {
|
|
18
|
+
await i18n.setLocale(stored)
|
|
19
|
+
}
|
|
20
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
// Persists the user's EXPLICIT language choice so it survives reloads. The app always
|
|
5
|
+
// boots in English (the `current` default) — there is no browser auto-detect — and only
|
|
6
|
+
// an explicit pick via the switcher changes this. A client plugin applies `current` to
|
|
7
|
+
// the active i18n locale on startup; the switcher writes here AND calls i18n's setLocale.
|
|
8
|
+
export const useLocaleStore = defineStore(
|
|
9
|
+
'locale',
|
|
10
|
+
() => {
|
|
11
|
+
const current = ref('en')
|
|
12
|
+
|
|
13
|
+
function set(code: string) {
|
|
14
|
+
current.value = code
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return { current, set }
|
|
18
|
+
},
|
|
19
|
+
{ persist: { pick: ['current'] } },
|
|
20
|
+
)
|
package/i18n/locales/en.json
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
+
"language": {
|
|
3
|
+
"switcher": "Language",
|
|
4
|
+
"warning": {
|
|
5
|
+
"title": "Unofficial translation",
|
|
6
|
+
"body": "This translation is community-provided and may be inaccurate. Spotted a mistake? Please report it or open a fix on {repoLink}.",
|
|
7
|
+
"repoLinkLabel": "the cat-factory repository",
|
|
8
|
+
"dismiss": "Dismiss"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
2
11
|
"common": {
|
|
3
12
|
"save": "Save",
|
|
4
13
|
"cancel": "Cancel",
|
package/i18n/locales/es.json
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
+
"language": {
|
|
3
|
+
"switcher": "Idioma",
|
|
4
|
+
"warning": {
|
|
5
|
+
"title": "Traducción no oficial",
|
|
6
|
+
"body": "Esta traducción es proporcionada por la comunidad y puede contener errores. ¿Has encontrado un error? Repórtalo o envía una corrección en {repoLink}.",
|
|
7
|
+
"repoLinkLabel": "el repositorio de cat-factory",
|
|
8
|
+
"dismiss": "Descartar"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
2
11
|
"common": {
|
|
3
12
|
"save": "Guardar",
|
|
4
13
|
"cancel": "Cancelar",
|
package/i18n/locales/fr.json
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
+
"language": {
|
|
3
|
+
"switcher": "Langue",
|
|
4
|
+
"warning": {
|
|
5
|
+
"title": "Traduction non officielle",
|
|
6
|
+
"body": "Cette traduction est fournie par la communauté et peut être inexacte. Vous avez repéré une erreur ? Signalez-la ou proposez une correction sur {repoLink}.",
|
|
7
|
+
"repoLinkLabel": "le dépôt cat-factory",
|
|
8
|
+
"dismiss": "Fermer"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
2
11
|
"common": {
|
|
3
12
|
"save": "Enregistrer",
|
|
4
13
|
"cancel": "Annuler",
|
package/i18n/locales/pl.json
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
+
"language": {
|
|
3
|
+
"switcher": "Język",
|
|
4
|
+
"warning": {
|
|
5
|
+
"title": "Nieoficjalne tłumaczenie",
|
|
6
|
+
"body": "To tłumaczenie pochodzi od społeczności i może być niedokładne. Zauważyłeś błąd? Zgłoś go lub prześlij poprawkę w {repoLink}.",
|
|
7
|
+
"repoLinkLabel": "repozytorium cat-factory",
|
|
8
|
+
"dismiss": "Odrzuć"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
2
11
|
"common": {
|
|
3
12
|
"save": "Zapisz",
|
|
4
13
|
"cancel": "Anuluj",
|
package/i18n/locales/uk.json
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
+
"language": {
|
|
3
|
+
"switcher": "Мова",
|
|
4
|
+
"warning": {
|
|
5
|
+
"title": "Неофіційний переклад",
|
|
6
|
+
"body": "Цей переклад надано спільнотою та може бути неточним. Помітили помилку? Повідомте про неї або надішліть виправлення у {repoLink}.",
|
|
7
|
+
"repoLinkLabel": "репозиторії cat-factory",
|
|
8
|
+
"dismiss": "Закрити"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
2
11
|
"common": {
|
|
3
12
|
"save": "Зберегти",
|
|
4
13
|
"cancel": "Скасувати",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.46.
|
|
3
|
+
"version": "0.46.2",
|
|
4
4
|
"description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|