@cat-factory/app 0.55.0 → 0.57.0
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/layout/AccountDeploymentSettings.vue +31 -2
- package/app/components/settings/InfrastructureBackendPicker.vue +8 -0
- package/app/composables/usePipelineErrorToast.ts +37 -4
- package/app/stores/ui.ts +21 -0
- package/i18n/locales/en.json +11 -4
- package/i18n/locales/es.json +5 -0
- package/i18n/locales/fr.json +5 -0
- package/i18n/locales/he.json +5 -0
- package/i18n/locales/pl.json +5 -0
- package/i18n/locales/uk.json +5 -0
- package/package.json +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed, onMounted, reactive, ref } from 'vue'
|
|
2
|
+
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'
|
|
3
3
|
import type { ContentStorageBackend, ContentStorageConfig } from '~/types/accountSettings'
|
|
4
4
|
|
|
5
5
|
// Deployment integration secrets for an account (admin only): the Slack app OAuth
|
|
@@ -11,9 +11,30 @@ import type { ContentStorageBackend, ContentStorageConfig } from '~/types/accoun
|
|
|
11
11
|
const props = defineProps<{ accountId: string }>()
|
|
12
12
|
|
|
13
13
|
const store = useAccountSettingsStore()
|
|
14
|
+
const ui = useUiStore()
|
|
14
15
|
const toast = useToast()
|
|
15
16
|
const { t } = useI18n()
|
|
16
17
|
|
|
18
|
+
// Deep-link anchor: the pipeline-start "configure storage" prompt opens this tab with the
|
|
19
|
+
// ui store's scroll target set to `content-storage`, so we bring the storage section (which
|
|
20
|
+
// sits at the bottom of a long tab) into view once rather than leaving the user to hunt for
|
|
21
|
+
// it. Scrolls after the section actually renders (it is gated on the async settings load).
|
|
22
|
+
const storageSection = ref<HTMLElement | null>(null)
|
|
23
|
+
async function maybeScrollToStorage() {
|
|
24
|
+
if (ui.accountSettingsScrollTarget !== 'content-storage') return
|
|
25
|
+
await nextTick()
|
|
26
|
+
const el = storageSection.value
|
|
27
|
+
if (!el) return
|
|
28
|
+
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
29
|
+
ui.clearAccountSettingsScrollTarget()
|
|
30
|
+
}
|
|
31
|
+
watch(
|
|
32
|
+
() => ui.accountSettingsScrollTarget,
|
|
33
|
+
() => {
|
|
34
|
+
void maybeScrollToStorage()
|
|
35
|
+
},
|
|
36
|
+
)
|
|
37
|
+
|
|
17
38
|
const slack = reactive({ clientId: '', clientSecret: '', redirectUrl: '' })
|
|
18
39
|
const linear = reactive({ clientId: '', clientSecret: '', redirectUrl: '' })
|
|
19
40
|
const web = reactive({ braveApiKey: '', searxngUrl: '', searxngApiKey: '' })
|
|
@@ -71,6 +92,9 @@ onMounted(async () => {
|
|
|
71
92
|
try {
|
|
72
93
|
await store.load(props.accountId)
|
|
73
94
|
hydrateStorage()
|
|
95
|
+
// The storage section only renders once the settings load resolves, so attempt the
|
|
96
|
+
// deep-link scroll here (the up-front watcher misses the target set before mount).
|
|
97
|
+
void maybeScrollToStorage()
|
|
74
98
|
} catch (e) {
|
|
75
99
|
toast.add({
|
|
76
100
|
title: t('layout.accountDeployment.loadFailed'),
|
|
@@ -508,7 +532,12 @@ async function clearWeb() {
|
|
|
508
532
|
</section>
|
|
509
533
|
|
|
510
534
|
<!-- Content storage (binary artifacts / screenshots) -->
|
|
511
|
-
<section
|
|
535
|
+
<section
|
|
536
|
+
v-if="storageCapability"
|
|
537
|
+
id="content-storage"
|
|
538
|
+
ref="storageSection"
|
|
539
|
+
class="space-y-2 border-t border-slate-800 pt-6"
|
|
540
|
+
>
|
|
512
541
|
<div class="flex items-center gap-2">
|
|
513
542
|
<h4 class="text-sm font-semibold text-slate-200">
|
|
514
543
|
{{ t('layout.accountDeployment.contentStorage.title') }}
|
|
@@ -82,6 +82,12 @@ const K3S_KEYS = {
|
|
|
82
82
|
label: 'settings.infrastructure.executionBackend.k3s',
|
|
83
83
|
desc: 'settings.infrastructure.executionBackend.k3sDesc',
|
|
84
84
|
}
|
|
85
|
+
// The Docker Compose env backend is test-env-only (it stands the app's own compose stack up as
|
|
86
|
+
// a Tester target). Its desc carries the actionable "when to use this" + the local-only caveat.
|
|
87
|
+
const COMPOSE_KEYS = {
|
|
88
|
+
label: 'settings.infrastructure.testEnvBackend.compose',
|
|
89
|
+
desc: 'settings.infrastructure.testEnvBackend.composeDesc',
|
|
90
|
+
}
|
|
85
91
|
|
|
86
92
|
const cap = computed<{ available: BackendKind[]; active: BackendKind } | null>(() => {
|
|
87
93
|
const c = auth.infrastructure?.[props.axis]
|
|
@@ -136,6 +142,8 @@ const effectiveActive = computed<BackendKind | null>(() => {
|
|
|
136
142
|
function backendKindKeys(kind: string): { label: string; desc: string } | null {
|
|
137
143
|
if (kind === 'kubernetes') return KUBERNETES_KEYS[props.axis]
|
|
138
144
|
if (kind === 'manifest') return BUILTIN_KEYS[delegatedKind.value]
|
|
145
|
+
// Docker Compose only ever registers on the test-env axis (it's an EnvironmentProvider).
|
|
146
|
+
if (kind === 'compose' && props.axis === 'testEnv') return COMPOSE_KEYS
|
|
139
147
|
return null
|
|
140
148
|
}
|
|
141
149
|
|
|
@@ -28,10 +28,14 @@ interface ConflictDetails {
|
|
|
28
28
|
* fails THIS typecheck until it is mapped here. (The typed-message-keys feature can't see the
|
|
29
29
|
* `t()` lookup because the key is resolved at runtime via this map, not written as a literal —
|
|
30
30
|
* so the exhaustiveness of the map, not `t()`, is what makes a missing reason a build error.)
|
|
31
|
-
* `providers_unconfigured`
|
|
32
|
-
* key namespace, so
|
|
31
|
+
* `providers_unconfigured` and `binary_storage_unconfigured` are excluded: each has bespoke
|
|
32
|
+
* handling (a "configure X" action) + its own key namespace, so neither reaches the generic
|
|
33
|
+
* lookup below.
|
|
33
34
|
*/
|
|
34
|
-
const CONFLICT_TITLE_KEYS: Record<
|
|
35
|
+
const CONFLICT_TITLE_KEYS: Record<
|
|
36
|
+
Exclude<ConflictReason, 'providers_unconfigured' | 'binary_storage_unconfigured'>,
|
|
37
|
+
string
|
|
38
|
+
> = {
|
|
35
39
|
dependencies_unmet: 'errors.conflict.title.dependencies_unmet',
|
|
36
40
|
task_limit_reached: 'errors.conflict.title.task_limit_reached',
|
|
37
41
|
tester_infra_unsupported: 'errors.conflict.title.tester_infra_unsupported',
|
|
@@ -97,12 +101,41 @@ export function usePipelineErrorToast() {
|
|
|
97
101
|
return
|
|
98
102
|
}
|
|
99
103
|
|
|
104
|
+
// A pipeline step relies on binary-artifact storage (the UI Tester uploads screenshots)
|
|
105
|
+
// but the account has none configured. Explain it and offer the jump to the content-storage
|
|
106
|
+
// settings — the same shape as the providers-unconfigured case above. Prefer the localized
|
|
107
|
+
// body (it carries no runtime interpolation) so non-English users see translated copy; the
|
|
108
|
+
// raw backend prose is only the last-resort fallback when the locale lacks the key.
|
|
109
|
+
if (conflict?.reason === 'binary_storage_unconfigured') {
|
|
110
|
+
toast.add({
|
|
111
|
+
title: t('errors.conflict.binaryStorageUnconfigured.title'),
|
|
112
|
+
description: te('errors.conflict.binaryStorageUnconfigured.body')
|
|
113
|
+
? t('errors.conflict.binaryStorageUnconfigured.body')
|
|
114
|
+
: (conflict.message ?? t('errors.conflict.fallbackMessage')),
|
|
115
|
+
color: 'error',
|
|
116
|
+
icon: 'i-lucide-image',
|
|
117
|
+
actions: [
|
|
118
|
+
{
|
|
119
|
+
label: t('errors.conflict.binaryStorageUnconfigured.action'),
|
|
120
|
+
icon: 'i-lucide-settings',
|
|
121
|
+
onClick: () => ui.openContentStorageSettings(),
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
})
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
|
|
100
128
|
if (conflict) {
|
|
101
129
|
// Per-reason title key from the exhaustive map; fall back to the caller's title key when
|
|
102
130
|
// this reason has no mapped/translated copy (`te` = translation-exists, so a key missing
|
|
103
131
|
// in the active locale never leaks as raw text). An unknown reason isn't in the map.
|
|
104
132
|
const reasonKey =
|
|
105
|
-
CONFLICT_TITLE_KEYS[
|
|
133
|
+
CONFLICT_TITLE_KEYS[
|
|
134
|
+
conflict.reason as Exclude<
|
|
135
|
+
ConflictReason,
|
|
136
|
+
'providers_unconfigured' | 'binary_storage_unconfigured'
|
|
137
|
+
>
|
|
138
|
+
]
|
|
106
139
|
toast.add({
|
|
107
140
|
title: reasonKey && te(reasonKey) ? t(reasonKey) : t(fallbackTitleKey),
|
|
108
141
|
description: conflict.message ?? t('errors.conflict.fallbackMessage'),
|
package/app/stores/ui.ts
CHANGED
|
@@ -121,6 +121,11 @@ export const useUiStore = defineStore('ui', () => {
|
|
|
121
121
|
// straight to a tab.
|
|
122
122
|
const accountSettingsOpen = ref(false)
|
|
123
123
|
const accountSettingsTab = ref('team')
|
|
124
|
+
// A one-shot deep-link anchor: when a surface opens account settings AND wants to land on a
|
|
125
|
+
// specific section within the (long) tab body, it sets this to that section's id. The owning
|
|
126
|
+
// panel scrolls the matching element into view once, then calls `clearAccountSettingsScrollTarget`
|
|
127
|
+
// so a later plain open doesn't re-scroll. Null when no section was requested.
|
|
128
|
+
const accountSettingsScrollTarget = ref<string | null>(null)
|
|
124
129
|
// Observability integration: the post-release-health connection panel (Datadog
|
|
125
130
|
// today, pluggable). NB: distinct from `observabilityInstanceId` below, which is the
|
|
126
131
|
// LLM per-call observability panel.
|
|
@@ -464,8 +469,21 @@ export const useUiStore = defineStore('ui', () => {
|
|
|
464
469
|
accountSettingsTab.value = tab
|
|
465
470
|
accountSettingsOpen.value = true
|
|
466
471
|
}
|
|
472
|
+
// Deep-link to the content (binary-artifact) storage configuration, which lives near the
|
|
473
|
+
// bottom of the account settings' team tab (`AccountDeploymentSettings`). Used by the
|
|
474
|
+
// pipeline-start error prompt when a storage-reliant agent (the UI Tester) has no storage
|
|
475
|
+
// configured. Sets a scroll anchor so the panel brings the storage section into view rather
|
|
476
|
+
// than dropping the user at the top of the long team tab to hunt for it.
|
|
477
|
+
function openContentStorageSettings() {
|
|
478
|
+
accountSettingsScrollTarget.value = 'content-storage'
|
|
479
|
+
openAccountSettings('team')
|
|
480
|
+
}
|
|
481
|
+
function clearAccountSettingsScrollTarget() {
|
|
482
|
+
accountSettingsScrollTarget.value = null
|
|
483
|
+
}
|
|
467
484
|
function closeAccountSettings() {
|
|
468
485
|
accountSettingsOpen.value = false
|
|
486
|
+
accountSettingsScrollTarget.value = null
|
|
469
487
|
}
|
|
470
488
|
function setAccountSettingsTab(tab: string) {
|
|
471
489
|
accountSettingsTab.value = tab
|
|
@@ -664,6 +682,7 @@ export const useUiStore = defineStore('ui', () => {
|
|
|
664
682
|
workspaceSettingsTab,
|
|
665
683
|
accountSettingsOpen,
|
|
666
684
|
accountSettingsTab,
|
|
685
|
+
accountSettingsScrollTarget,
|
|
667
686
|
observabilityConnectionOpen,
|
|
668
687
|
infrastructureOpen,
|
|
669
688
|
infrastructureTab,
|
|
@@ -739,6 +758,8 @@ export const useUiStore = defineStore('ui', () => {
|
|
|
739
758
|
closeWorkspaceSettings,
|
|
740
759
|
setWorkspaceSettingsTab,
|
|
741
760
|
openAccountSettings,
|
|
761
|
+
openContentStorageSettings,
|
|
762
|
+
clearAccountSettingsScrollTarget,
|
|
742
763
|
closeAccountSettings,
|
|
743
764
|
setAccountSettingsTab,
|
|
744
765
|
openObservabilityConnection,
|
package/i18n/locales/en.json
CHANGED
|
@@ -292,6 +292,11 @@
|
|
|
292
292
|
"description": "Keep the named placeholder for the model list intact (a comma-separated list of model ids is injected at runtime)."
|
|
293
293
|
},
|
|
294
294
|
"action": "Configure AI"
|
|
295
|
+
},
|
|
296
|
+
"binaryStorageUnconfigured": {
|
|
297
|
+
"title": "No storage for this pipeline",
|
|
298
|
+
"body": "This pipeline includes an agent that needs binary storage (the UI Tester uploads its screenshots), but this account has no content storage configured. Configure content storage to run it.",
|
|
299
|
+
"action": "Configure storage"
|
|
295
300
|
}
|
|
296
301
|
}
|
|
297
302
|
},
|
|
@@ -1287,12 +1292,14 @@
|
|
|
1287
1292
|
},
|
|
1288
1293
|
"testEnvBackend": {
|
|
1289
1294
|
"label": "Where test environments run",
|
|
1290
|
-
"local-compose": "In-container docker-compose",
|
|
1291
|
-
"local-composeDesc": "Stand the Tester's dependencies up with docker-compose inside the run's container.",
|
|
1295
|
+
"local-compose": "In-container docker-compose (Tester deps)",
|
|
1296
|
+
"local-composeDesc": "Stand the Tester's dependencies up with docker-compose inside the run's container. For a full app preview env, use the Docker Compose preview env option below.",
|
|
1292
1297
|
"kubernetes": "Kubernetes cluster",
|
|
1293
|
-
"kubernetesDesc": "Provision a per-PR namespace in a Kubernetes cluster you operate.",
|
|
1298
|
+
"kubernetesDesc": "Provision a per-PR namespace in a Kubernetes cluster you operate. Best for Kubernetes-native apps or when you already run a cluster.",
|
|
1294
1299
|
"environment-provider": "Custom HTTP provider",
|
|
1295
|
-
"environment-providerDesc": "Provision ephemeral environments through your own HTTP management API."
|
|
1300
|
+
"environment-providerDesc": "Provision ephemeral environments through your own HTTP management API. Best when you already have bespoke preview-env tooling.",
|
|
1301
|
+
"compose": "Docker Compose preview env",
|
|
1302
|
+
"composeDesc": "Stand the repo's own docker-compose.yml up on a local Docker daemon as the Tester's URL. Best for local Compose-based apps. Image-based stacks only; needs a Docker daemon, so local deployments only."
|
|
1296
1303
|
}
|
|
1297
1304
|
},
|
|
1298
1305
|
"providerConnection": {
|
package/i18n/locales/es.json
CHANGED
|
@@ -259,6 +259,11 @@
|
|
|
259
259
|
"title": "No hay proveedor de IA para este modelo",
|
|
260
260
|
"body": "No hay ningún proveedor configurado para {models}. Añade una clave de proveedor, conecta una suscripción o activa Cloudflare AI para ejecutarlo.",
|
|
261
261
|
"action": "Configurar IA"
|
|
262
|
+
},
|
|
263
|
+
"binaryStorageUnconfigured": {
|
|
264
|
+
"title": "No hay almacenamiento para esta canalización",
|
|
265
|
+
"body": "Esta canalización incluye un agente que necesita almacenamiento binario (el probador de UI sube sus capturas de pantalla), pero esta cuenta no tiene almacenamiento de contenido configurado. Configura el almacenamiento de contenido para ejecutarla.",
|
|
266
|
+
"action": "Configurar almacenamiento"
|
|
262
267
|
}
|
|
263
268
|
}
|
|
264
269
|
},
|
package/i18n/locales/fr.json
CHANGED
|
@@ -259,6 +259,11 @@
|
|
|
259
259
|
"title": "Aucun fournisseur d’IA pour ce modèle",
|
|
260
260
|
"body": "Aucun fournisseur n’est configuré pour {models}. Ajoutez une clé de fournisseur, connectez un abonnement ou activez Cloudflare AI pour l’exécuter.",
|
|
261
261
|
"action": "Configurer l’IA"
|
|
262
|
+
},
|
|
263
|
+
"binaryStorageUnconfigured": {
|
|
264
|
+
"title": "Aucun stockage pour ce pipeline",
|
|
265
|
+
"body": "Ce pipeline inclut un agent qui a besoin d’un stockage binaire (le testeur d’UI téléverse ses captures d’écran), mais aucun stockage de contenu n’est configuré pour ce compte. Configurez le stockage de contenu pour l’exécuter.",
|
|
266
|
+
"action": "Configurer le stockage"
|
|
262
267
|
}
|
|
263
268
|
}
|
|
264
269
|
},
|
package/i18n/locales/he.json
CHANGED
|
@@ -259,6 +259,11 @@
|
|
|
259
259
|
"title": "אין ספק AI למודל זה",
|
|
260
260
|
"body": "לא מוגדר ספק עבור {models}. הוסף מפתח ספק, חבר מנוי, או הפעל את Cloudflare AI כדי להריץ אותו.",
|
|
261
261
|
"action": "הגדר AI"
|
|
262
|
+
},
|
|
263
|
+
"binaryStorageUnconfigured": {
|
|
264
|
+
"title": "אין אחסון לצינור הזה",
|
|
265
|
+
"body": "צינור זה כולל סוכן שזקוק לאחסון בינארי (בודק ה-UI מעלה את צילומי המסך שלו), אך לחשבון זה לא מוגדר אחסון תוכן. הגדר אחסון תוכן כדי להריץ אותו.",
|
|
266
|
+
"action": "הגדר אחסון"
|
|
262
267
|
}
|
|
263
268
|
}
|
|
264
269
|
},
|
package/i18n/locales/pl.json
CHANGED
|
@@ -259,6 +259,11 @@
|
|
|
259
259
|
"title": "Brak dostawcy AI dla tego modelu",
|
|
260
260
|
"body": "Dla {models} nie skonfigurowano żadnego dostawcy. Dodaj klucz dostawcy, połącz subskrypcję lub włącz Cloudflare AI, aby go uruchomić.",
|
|
261
261
|
"action": "Skonfiguruj AI"
|
|
262
|
+
},
|
|
263
|
+
"binaryStorageUnconfigured": {
|
|
264
|
+
"title": "Brak magazynu dla tego potoku",
|
|
265
|
+
"body": "Ten potok zawiera agenta, który wymaga magazynu binarnego (tester UI przesyła swoje zrzuty ekranu), ale to konto nie ma skonfigurowanego magazynu treści. Skonfiguruj magazyn treści, aby go uruchomić.",
|
|
266
|
+
"action": "Skonfiguruj magazyn"
|
|
262
267
|
}
|
|
263
268
|
}
|
|
264
269
|
},
|
package/i18n/locales/uk.json
CHANGED
|
@@ -259,6 +259,11 @@
|
|
|
259
259
|
"title": "Немає постачальника ШІ для цієї моделі",
|
|
260
260
|
"body": "Для {models} не налаштовано жодного постачальника. Додайте ключ постачальника, підключіть підписку або увімкніть Cloudflare AI, щоб запустити її.",
|
|
261
261
|
"action": "Налаштувати ШІ"
|
|
262
|
+
},
|
|
263
|
+
"binaryStorageUnconfigured": {
|
|
264
|
+
"title": "Немає сховища для цього конвеєра",
|
|
265
|
+
"body": "Цей конвеєр містить агента, якому потрібне бінарне сховище (тестувальник інтерфейсу завантажує свої знімки екрана), але для цього облікового запису не налаштовано сховище вмісту. Налаштуйте сховище вмісту, щоб запустити його.",
|
|
266
|
+
"action": "Налаштувати сховище"
|
|
262
267
|
}
|
|
263
268
|
}
|
|
264
269
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.57.0",
|
|
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",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"valibot": "^1.4.2",
|
|
35
35
|
"vue": "^3.5.39",
|
|
36
36
|
"wretch": "^3.0.9",
|
|
37
|
-
"@cat-factory/contracts": "0.
|
|
37
|
+
"@cat-factory/contracts": "0.58.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@toad-contracts/testing": "0.3.2",
|