@cat-factory/app 0.55.0 → 0.56.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/composables/usePipelineErrorToast.ts +37 -4
- package/app/stores/ui.ts +21 -0
- package/i18n/locales/en.json +5 -0
- 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') }}
|
|
@@ -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
|
},
|
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.56.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",
|