@cat-factory/app 0.149.0 → 0.150.1

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.
@@ -186,200 +186,231 @@ export function parseConflict(
186
186
  }
187
187
  }
188
188
 
189
+ /** The non-null parsed shape of a backend conflict, as returned by {@link parseConflict}. */
190
+ type ParsedConflict = NonNullable<ReturnType<typeof parseConflict>>
191
+
189
192
  export function usePipelineErrorToast() {
190
193
  const toast = useToast()
191
194
  const ui = useUiStore()
192
195
  const { t, te } = useI18n()
193
196
 
194
- /**
195
- * Present `error` as a toast. `fallbackTitleKey` is an i18n message key used for
196
- * non-conflict failures and any conflict reason without a dedicated title.
197
- */
198
- function present(error: unknown, fallbackTitleKey = 'common.actionFailed'): void {
199
- const conflict = parseConflict(error)
197
+ // The headline case: a pipeline step's model has no usable provider. Name the
198
+ // offending model(s), explain no provider is available, and offer the one-click jump
199
+ // to the AI setup the same remedy the startup "No AI model configured" banner gives.
200
+ function presentProvidersUnconfigured(conflict: ParsedConflict): void {
201
+ const models = Array.isArray(conflict.details.models) ? conflict.details.models : []
202
+ const list = models.join(', ')
203
+ toast.add({
204
+ title: t('errors.conflict.providersUnconfigured.title'),
205
+ description: list
206
+ ? t('errors.conflict.providersUnconfigured.body', { models: list })
207
+ : (conflict.message ?? t('errors.conflict.fallbackMessage')),
208
+ color: 'error',
209
+ icon: 'i-lucide-cpu',
210
+ // Stay until dismissed: an actionable toast whose remedy button vanishes on the ~5s
211
+ // auto-dismiss takes the one-click fix with it before the user can reach it.
212
+ duration: 0,
213
+ actions: [
214
+ {
215
+ label: t('errors.conflict.providersUnconfigured.action'),
216
+ icon: 'i-lucide-settings',
217
+ onClick: () => ui.openAiProviderSetup(),
218
+ },
219
+ ],
220
+ })
221
+ }
200
222
 
201
- // The headline case: a pipeline step's model has no usable provider. Name the
202
- // offending model(s), explain no provider is available, and offer the one-click jump
203
- // to the AI setup — the same remedy the startup "No AI model configured" banner gives.
204
- if (conflict?.reason === 'providers_unconfigured') {
205
- const models = Array.isArray(conflict.details.models) ? conflict.details.models : []
206
- const list = models.join(', ')
207
- toast.add({
208
- title: t('errors.conflict.providersUnconfigured.title'),
209
- description: list
210
- ? t('errors.conflict.providersUnconfigured.body', { models: list })
211
- : (conflict.message ?? t('errors.conflict.fallbackMessage')),
212
- color: 'error',
213
- icon: 'i-lucide-cpu',
214
- // Stay until dismissed: an actionable toast whose remedy button vanishes on the ~5s
215
- // auto-dismiss takes the one-click fix with it before the user can reach it.
216
- duration: 0,
217
- actions: [
218
- {
219
- label: t('errors.conflict.providersUnconfigured.action'),
220
- icon: 'i-lucide-settings',
221
- onClick: () => ui.openAiProviderSetup(),
222
- },
223
- ],
224
- })
225
- return
226
- }
223
+ // A pipeline step relies on binary-artifact storage (the UI Tester uploads screenshots)
224
+ // but the account has none configured. Explain it and offer the jump to the content-storage
225
+ // settings — the same shape as the providers-unconfigured case above. Prefer the localized
226
+ // body (it carries no runtime interpolation) so non-English users see translated copy; the
227
+ // raw backend prose is only the last-resort fallback when the locale lacks the key.
228
+ function presentBinaryStorageUnconfigured(conflict: ParsedConflict): void {
229
+ toast.add({
230
+ title: t('errors.conflict.binaryStorageUnconfigured.title'),
231
+ description: te('errors.conflict.binaryStorageUnconfigured.body')
232
+ ? t('errors.conflict.binaryStorageUnconfigured.body')
233
+ : (conflict.message ?? t('errors.conflict.fallbackMessage')),
234
+ color: 'error',
235
+ icon: 'i-lucide-image',
236
+ // Sticky, like the providers-unconfigured toast above: keep the "Configure storage"
237
+ // remedy reachable instead of letting it auto-dismiss.
238
+ duration: 0,
239
+ actions: [
240
+ {
241
+ label: t('errors.conflict.binaryStorageUnconfigured.action'),
242
+ icon: 'i-lucide-settings',
243
+ onClick: () => ui.openContentStorageSettings(),
244
+ },
245
+ ],
246
+ })
247
+ }
227
248
 
228
- // A pipeline step relies on binary-artifact storage (the UI Tester uploads screenshots)
229
- // but the account has none configured. Explain it and offer the jump to the content-storage
230
- // settings — the same shape as the providers-unconfigured case above. Prefer the localized
231
- // body (it carries no runtime interpolation) so non-English users see translated copy; the
232
- // raw backend prose is only the last-resort fallback when the locale lacks the key.
233
- if (conflict?.reason === 'binary_storage_unconfigured') {
234
- toast.add({
235
- title: t('errors.conflict.binaryStorageUnconfigured.title'),
236
- description: te('errors.conflict.binaryStorageUnconfigured.body')
237
- ? t('errors.conflict.binaryStorageUnconfigured.body')
238
- : (conflict.message ?? t('errors.conflict.fallbackMessage')),
239
- color: 'error',
240
- icon: 'i-lucide-image',
241
- // Sticky, like the providers-unconfigured toast above: keep the "Configure storage"
242
- // remedy reachable instead of letting it auto-dismiss.
243
- duration: 0,
244
- actions: [
245
- {
246
- label: t('errors.conflict.binaryStorageUnconfigured.action'),
247
- icon: 'i-lucide-settings',
248
- onClick: () => ui.openContentStorageSettings(),
249
+ // A pipeline includes a Deployer, but the SERVICE's ephemeral-environment config (the in-repo
250
+ // "what/where") is incomplete for its declared type. Steer the user straight to THAT service's
251
+ // environment config — the compose wizard for docker-compose, the service inspector otherwise
252
+ // falling back to the workspace infrastructure window if the frame id wasn't carried.
253
+ function presentDeployerServiceConfig(conflict: ParsedConflict): void {
254
+ const frameId =
255
+ typeof conflict.details.frameId === 'string' ? conflict.details.frameId : undefined
256
+ const provisionType =
257
+ typeof conflict.details.provisionType === 'string'
258
+ ? conflict.details.provisionType
259
+ : undefined
260
+ const missing = Array.isArray(conflict.details.missing)
261
+ ? conflict.details.missing.join(', ')
262
+ : ''
263
+ toast.add({
264
+ title: t('errors.conflict.deployerServiceConfig.title'),
265
+ description: missing
266
+ ? t('errors.conflict.deployerServiceConfig.body', { missing })
267
+ : (conflict.message ?? t('errors.conflict.fallbackMessage')),
268
+ color: 'error',
269
+ icon: 'i-lucide-server',
270
+ // Sticky, like the other actionable conflicts: keep the "Fix configuration" jump reachable.
271
+ duration: 0,
272
+ actions: [
273
+ {
274
+ label: t('errors.conflict.deployerServiceConfig.action'),
275
+ icon: 'i-lucide-settings',
276
+ onClick: () => {
277
+ if (frameId && provisionType === 'docker-compose') ui.openEnvironmentSetup(frameId)
278
+ else if (frameId) ui.select(frameId)
279
+ else ui.openProviderConnection('environment')
249
280
  },
250
- ],
251
- })
252
- return
253
- }
281
+ },
282
+ ],
283
+ })
284
+ }
254
285
 
255
- // A pipeline includes a Deployer, but the SERVICE's ephemeral-environment config (the in-repo
256
- // "what/where") is incomplete for its declared type. Steer the user straight to THAT service's
257
- // environment config the compose wizard for docker-compose, the service inspector otherwise
258
- // falling back to the workspace infrastructure window if the frame id wasn't carried.
259
- if (conflict?.reason === 'deployer_service_provisioning_incomplete') {
260
- const frameId =
261
- typeof conflict.details.frameId === 'string' ? conflict.details.frameId : undefined
262
- const provisionType =
263
- typeof conflict.details.provisionType === 'string'
264
- ? conflict.details.provisionType
265
- : undefined
266
- const missing = Array.isArray(conflict.details.missing)
267
- ? conflict.details.missing.join(', ')
268
- : ''
269
- toast.add({
270
- title: t('errors.conflict.deployerServiceConfig.title'),
271
- description: missing
272
- ? t('errors.conflict.deployerServiceConfig.body', { missing })
273
- : (conflict.message ?? t('errors.conflict.fallbackMessage')),
274
- color: 'error',
275
- icon: 'i-lucide-server',
276
- // Sticky, like the other actionable conflicts: keep the "Fix configuration" jump reachable.
277
- duration: 0,
278
- actions: [
279
- {
280
- label: t('errors.conflict.deployerServiceConfig.action'),
281
- icon: 'i-lucide-settings',
282
- onClick: () => {
283
- if (frameId && provisionType === 'docker-compose') ui.openEnvironmentSetup(frameId)
284
- else if (frameId) ui.select(frameId)
285
- else ui.openProviderConnection('environment')
286
- },
287
- },
288
- ],
289
- })
290
- return
291
- }
286
+ // A pipeline includes a Deployer and the service config is sound, but no WORKSPACE handler
287
+ // resolves for the service's provision type (missing or ambiguous). Steer to the Infrastructure
288
+ // window's Test-environments tab. (Also raised by the Tester start gate same fix applies.)
289
+ function presentProvisionTypeUnhandled(conflict: ParsedConflict): void {
290
+ const type =
291
+ typeof conflict.details.provisionType === 'string' ? conflict.details.provisionType : ''
292
+ toast.add({
293
+ title: t('errors.conflict.provisionTypeUnhandled.title'),
294
+ description: type
295
+ ? t('errors.conflict.provisionTypeUnhandled.body', { type })
296
+ : (conflict.message ?? t('errors.conflict.fallbackMessage')),
297
+ color: 'error',
298
+ icon: 'i-lucide-server-cog',
299
+ duration: 0,
300
+ actions: [
301
+ {
302
+ label: t('errors.conflict.provisionTypeUnhandled.action'),
303
+ icon: 'i-lucide-settings',
304
+ onClick: () => ui.openProviderConnection('environment'),
305
+ },
306
+ ],
307
+ })
308
+ }
292
309
 
293
- // A pipeline includes a Deployer and the service config is sound, but no WORKSPACE handler
294
- // resolves for the service's provision type (missing or ambiguous). Steer to the Infrastructure
295
- // window's Test-environments tab. (Also raised by the Tester start gate same fix applies.)
296
- if (conflict?.reason === 'provision_type_unhandled') {
297
- const type =
298
- typeof conflict.details.provisionType === 'string' ? conflict.details.provisionType : ''
299
- toast.add({
300
- title: t('errors.conflict.provisionTypeUnhandled.title'),
301
- description: type
302
- ? t('errors.conflict.provisionTypeUnhandled.body', { type })
303
- : (conflict.message ?? t('errors.conflict.fallbackMessage')),
304
- color: 'error',
305
- icon: 'i-lucide-server-cog',
306
- duration: 0,
307
- actions: [
308
- {
309
- label: t('errors.conflict.provisionTypeUnhandled.action'),
310
- icon: 'i-lucide-settings',
311
- onClick: () => ui.openProviderConnection('environment'),
312
- },
313
- ],
314
- })
315
- return
310
+ // A pipeline includes a Deployer, the config is structurally complete, but the live connection
311
+ // probe of the resolved deployment integration failed (unreachable endpoint / apiserver, bad
312
+ // token). Surface the provider's failure detail and steer to the handler to fix + re-test it.
313
+ function presentDeployerConnectionFailed(conflict: ParsedConflict): void {
314
+ const detail = typeof conflict.details.detail === 'string' ? conflict.details.detail : undefined
315
+ toast.add({
316
+ title: t('errors.conflict.deployerConnectionFailed.title'),
317
+ description: detail
318
+ ? t('errors.conflict.deployerConnectionFailed.body', { detail })
319
+ : (conflict.message ?? t('errors.conflict.fallbackMessage')),
320
+ color: 'error',
321
+ icon: 'i-lucide-plug',
322
+ duration: 0,
323
+ actions: [
324
+ {
325
+ label: t('errors.conflict.deployerConnectionFailed.action'),
326
+ icon: 'i-lucide-settings',
327
+ onClick: () => ui.openProviderConnection('environment'),
328
+ },
329
+ ],
330
+ })
331
+ }
332
+
333
+ /**
334
+ * Dispatch the bespoke conflict reasons (a runtime-interpolated body + a "configure X" action,
335
+ * each with its own key namespace — the ones excluded from `CONFLICT_INFO`). Returns `true` when
336
+ * the reason was one of them (and the toast was raised), `false` to fall through to the generic
337
+ * map. The reason values are mutually exclusive, so dispatch order is irrelevant.
338
+ */
339
+ function presentBespokeConflict(conflict: ParsedConflict): boolean {
340
+ switch (conflict.reason) {
341
+ case 'providers_unconfigured':
342
+ presentProvidersUnconfigured(conflict)
343
+ return true
344
+ case 'binary_storage_unconfigured':
345
+ presentBinaryStorageUnconfigured(conflict)
346
+ return true
347
+ case 'deployer_service_provisioning_incomplete':
348
+ presentDeployerServiceConfig(conflict)
349
+ return true
350
+ case 'provision_type_unhandled':
351
+ presentProvisionTypeUnhandled(conflict)
352
+ return true
353
+ case 'deployer_connection_test_failed':
354
+ presentDeployerConnectionFailed(conflict)
355
+ return true
356
+ default:
357
+ return false
316
358
  }
359
+ }
317
360
 
318
- // A pipeline includes a Deployer, the config is structurally complete, but the live connection
319
- // probe of the resolved deployment integration failed (unreachable endpoint / apiserver, bad
320
- // token). Surface the provider's failure detail and steer to the handler to fix + re-test it.
321
- if (conflict?.reason === 'deployer_connection_test_failed') {
322
- const detail =
323
- typeof conflict.details.detail === 'string' ? conflict.details.detail : undefined
361
+ /**
362
+ * Per-reason copy from the exhaustive map: a translated title + description, and a jump
363
+ * action for the reasons a UI panel can fix. `te` (translation-exists) guards every lookup,
364
+ * so a key missing from the active locale falls back rather than leaking a raw key: the
365
+ * title falls to the caller's key, the description to the raw backend `message`. An unknown
366
+ * reason (not in the map) gets the same generic title + raw-message fallback.
367
+ */
368
+ function presentMappedConflict(conflict: ParsedConflict, fallbackTitleKey: string): void {
369
+ const info = conflict.reason
370
+ ? CONFLICT_INFO[conflict.reason as Exclude<ConflictReason, BespokeConflictReason>]
371
+ : undefined
372
+ if (info) {
324
373
  toast.add({
325
- title: t('errors.conflict.deployerConnectionFailed.title'),
326
- description: detail
327
- ? t('errors.conflict.deployerConnectionFailed.body', { detail })
374
+ title: te(info.titleKey) ? t(info.titleKey) : t(fallbackTitleKey),
375
+ description: te(info.descriptionKey)
376
+ ? t(info.descriptionKey)
328
377
  : (conflict.message ?? t('errors.conflict.fallbackMessage')),
329
- color: 'error',
330
- icon: 'i-lucide-plug',
331
- duration: 0,
332
- actions: [
333
- {
334
- label: t('errors.conflict.deployerConnectionFailed.action'),
335
- icon: 'i-lucide-settings',
336
- onClick: () => ui.openProviderConnection('environment'),
337
- },
338
- ],
378
+ color: 'warning',
379
+ icon: 'i-lucide-triangle-alert',
380
+ // A reason with a jump action becomes an actionable, sticky toast (like the bespoke
381
+ // conflicts above) so the one-click remedy doesn't auto-dismiss before it's reached.
382
+ ...(info.action
383
+ ? {
384
+ duration: 0,
385
+ actions: [
386
+ {
387
+ label: t(info.action.labelKey),
388
+ icon: info.action.icon,
389
+ onClick: () => info.action?.run(ui),
390
+ },
391
+ ],
392
+ }
393
+ : {}),
339
394
  })
340
395
  return
341
396
  }
397
+ toast.add({
398
+ title: t(fallbackTitleKey),
399
+ description: conflict.message ?? t('errors.conflict.fallbackMessage'),
400
+ color: 'warning',
401
+ icon: 'i-lucide-triangle-alert',
402
+ })
403
+ }
342
404
 
405
+ /**
406
+ * Present `error` as a toast. `fallbackTitleKey` is an i18n message key used for
407
+ * non-conflict failures and any conflict reason without a dedicated title.
408
+ */
409
+ function present(error: unknown, fallbackTitleKey = 'common.actionFailed'): void {
410
+ const conflict = parseConflict(error)
343
411
  if (conflict) {
344
- // Per-reason copy from the exhaustive map: a translated title + description, and a jump
345
- // action for the reasons a UI panel can fix. `te` (translation-exists) guards every lookup,
346
- // so a key missing from the active locale falls back rather than leaking a raw key: the
347
- // title falls to the caller's key, the description to the raw backend `message`. An unknown
348
- // reason (not in the map) gets the same generic title + raw-message fallback.
349
- const info = conflict.reason
350
- ? CONFLICT_INFO[conflict.reason as Exclude<ConflictReason, BespokeConflictReason>]
351
- : undefined
352
- if (info) {
353
- toast.add({
354
- title: te(info.titleKey) ? t(info.titleKey) : t(fallbackTitleKey),
355
- description: te(info.descriptionKey)
356
- ? t(info.descriptionKey)
357
- : (conflict.message ?? t('errors.conflict.fallbackMessage')),
358
- color: 'warning',
359
- icon: 'i-lucide-triangle-alert',
360
- // A reason with a jump action becomes an actionable, sticky toast (like the bespoke
361
- // conflicts above) so the one-click remedy doesn't auto-dismiss before it's reached.
362
- ...(info.action
363
- ? {
364
- duration: 0,
365
- actions: [
366
- {
367
- label: t(info.action.labelKey),
368
- icon: info.action.icon,
369
- onClick: () => info.action?.run(ui),
370
- },
371
- ],
372
- }
373
- : {}),
374
- })
375
- return
376
- }
377
- toast.add({
378
- title: t(fallbackTitleKey),
379
- description: conflict.message ?? t('errors.conflict.fallbackMessage'),
380
- color: 'warning',
381
- icon: 'i-lucide-triangle-alert',
382
- })
412
+ if (presentBespokeConflict(conflict)) return
413
+ presentMappedConflict(conflict, fallbackTitleKey)
383
414
  return
384
415
  }
385
416
 
@@ -1,6 +1,6 @@
1
1
  import { defineStore } from 'pinia'
2
2
  import { computed, ref } from 'vue'
3
- import type { AddApiKeyInput, ApiKey, ApiKeyProvider } from '~/types/domain'
3
+ import type { AddApiKeyInput, ApiKey, ApiKeyProvider, UpdateApiKeyInput } from '~/types/domain'
4
4
 
5
5
  /**
6
6
  * The direct-provider API keys reachable from a workspace: the workspace's own keys
@@ -48,12 +48,30 @@ export const useApiKeysStore = defineStore('apiKeys', () => {
48
48
  workspaceKeys.value = workspaceKeys.value.filter((k) => k.id !== id)
49
49
  }
50
50
 
51
+ // Pinning a default clears any prior default of the same scope+provider server-side, so a
52
+ // default change reloads the affected scope to reflect the single-default invariant; a plain
53
+ // enable/disable patches the one row in place.
54
+ async function updateWorkspaceKey(id: string, patch: UpdateApiKeyInput) {
55
+ if (!workspaceId.value) return
56
+ const updated = await api.updateWorkspaceApiKey(workspaceId.value, id, patch)
57
+ if (patch.isDefault !== undefined) await load(workspaceId.value)
58
+ else workspaceKeys.value = workspaceKeys.value.map((k) => (k.id === id ? updated : k))
59
+ return updated
60
+ }
61
+
51
62
  async function addUserKey(input: AddApiKeyInput) {
52
63
  const created = await api.addMyApiKey(input)
53
64
  userKeys.value = [...userKeys.value, created]
54
65
  return created
55
66
  }
56
67
 
68
+ async function updateUserKey(id: string, patch: UpdateApiKeyInput) {
69
+ const updated = await api.updateMyApiKey(id, patch)
70
+ if (patch.isDefault !== undefined && workspaceId.value) await load(workspaceId.value)
71
+ else userKeys.value = userKeys.value.map((k) => (k.id === id ? updated : k))
72
+ return updated
73
+ }
74
+
57
75
  async function removeUserKey(id: string) {
58
76
  await api.removeMyApiKey(id)
59
77
  userKeys.value = userKeys.value.filter((k) => k.id !== id)
@@ -72,6 +90,14 @@ export const useApiKeysStore = defineStore('apiKeys', () => {
72
90
  accountKeys.value = [...accountKeys.value, created]
73
91
  }
74
92
 
93
+ async function updateAccountKey(id: string, patch: UpdateApiKeyInput) {
94
+ if (!accountId.value) return
95
+ const updated = await api.updateAccountApiKey(accountId.value, id, patch)
96
+ if (patch.isDefault !== undefined) await loadAccountKeys(accountId.value)
97
+ else accountKeys.value = accountKeys.value.map((k) => (k.id === id ? updated : k))
98
+ return updated
99
+ }
100
+
75
101
  async function removeAccountKey(id: string) {
76
102
  if (!accountId.value) return
77
103
  await api.removeAccountApiKey(accountId.value, id)
@@ -98,11 +124,14 @@ export const useApiKeysStore = defineStore('apiKeys', () => {
98
124
  loading,
99
125
  load,
100
126
  addWorkspaceKey,
127
+ updateWorkspaceKey,
101
128
  removeWorkspaceKey,
102
129
  addUserKey,
130
+ updateUserKey,
103
131
  removeUserKey,
104
132
  loadAccountKeys,
105
133
  addAccountKey,
134
+ updateAccountKey,
106
135
  removeAccountKey,
107
136
  configuredProviders,
108
137
  hasProvider,
@@ -1,6 +1,10 @@
1
1
  import { defineStore } from 'pinia'
2
2
  import { computed, ref } from 'vue'
3
- import type { SubscriptionVendor, VendorCredential } from '~/types/domain'
3
+ import type {
4
+ SubscriptionVendor,
5
+ UpdateVendorCredentialInput,
6
+ VendorCredential,
7
+ } from '~/types/domain'
4
8
 
5
9
  /**
6
10
  * The workspace's connected LLM-vendor subscription credentials (the token pool
@@ -33,6 +37,22 @@ export const useVendorCredentialsStore = defineStore('vendorCredentials', () =>
33
37
  credentials.value = [...credentials.value, created]
34
38
  }
35
39
 
40
+ /**
41
+ * Enable/disable and/or (un)pin a token as its vendor's default. Because pinning a
42
+ * default clears any prior default of the same vendor server-side, we reload the pool so
43
+ * the whole vendor group reflects the single-default invariant rather than patching one row.
44
+ */
45
+ async function update(id: string, patch: UpdateVendorCredentialInput) {
46
+ if (!workspaceId.value) return
47
+ const updated = await api.updateVendorCredential(workspaceId.value, id, patch)
48
+ if (patch.isDefault !== undefined) {
49
+ await load(workspaceId.value)
50
+ } else {
51
+ credentials.value = credentials.value.map((c) => (c.id === id ? updated : c))
52
+ }
53
+ return updated
54
+ }
55
+
36
56
  async function remove(id: string) {
37
57
  if (!workspaceId.value) return
38
58
  await api.removeVendorCredential(workspaceId.value, id)
@@ -50,5 +70,15 @@ export const useVendorCredentialsStore = defineStore('vendorCredentials', () =>
50
70
  return credentials.value.filter((c) => c.vendor === vendor)
51
71
  }
52
72
 
53
- return { credentials, loading, load, add, remove, configuredVendors, hasVendor, forVendor }
73
+ return {
74
+ credentials,
75
+ loading,
76
+ load,
77
+ add,
78
+ update,
79
+ remove,
80
+ configuredVendors,
81
+ hasVendor,
82
+ forVendor,
83
+ }
54
84
  })
@@ -15,6 +15,8 @@ export type {
15
15
  ApiKeyProvider,
16
16
  ApiKey,
17
17
  AddApiKeyInput,
18
+ UpdateApiKeyInput,
18
19
  VendorCredential,
20
+ UpdateVendorCredentialInput,
19
21
  PromptFragment,
20
22
  } from '@cat-factory/contracts'
@@ -2452,7 +2452,8 @@
2452
2452
  "toast": {
2453
2453
  "connected": "API-Schlüssel verbunden",
2454
2454
  "connectFailed": "Schlüssel konnte nicht verbunden werden",
2455
- "removeFailed": "Schlüssel konnte nicht entfernt werden"
2455
+ "removeFailed": "Schlüssel konnte nicht entfernt werden",
2456
+ "updateFailed": "Schlüssel konnte nicht aktualisiert werden"
2456
2457
  },
2457
2458
  "providers": {
2458
2459
  "openai": {
@@ -2491,7 +2492,11 @@
2491
2492
  "step2": "Die Basis-URL des Gateways wird von Ihrem Deployment-Betreiber festgelegt (LITELLM_BASE_URL), nicht hier."
2492
2493
  }
2493
2494
  },
2494
- "keyNoun": "diesen API-Schlüssel"
2495
+ "keyNoun": "diesen API-Schlüssel",
2496
+ "defaultBadge": "Standard",
2497
+ "pinDefault": "Als Standard festlegen",
2498
+ "disabledBadge": "Deaktiviert",
2499
+ "enableToggle": "Diesen Schlüssel aktivieren oder deaktivieren"
2495
2500
  },
2496
2501
  "vendorCredentials": {
2497
2502
  "title": "LLM-Vendors",
@@ -2514,7 +2519,8 @@
2514
2519
  "toast": {
2515
2520
  "connected": "Token verbunden",
2516
2521
  "connectFailed": "Token konnte nicht verbunden werden",
2517
- "removeFailed": "Token konnte nicht entfernt werden"
2522
+ "removeFailed": "Token konnte nicht entfernt werden",
2523
+ "updateFailed": "Token konnte nicht aktualisiert werden"
2518
2524
  },
2519
2525
  "vendors": {
2520
2526
  "kimi": {
@@ -2533,7 +2539,11 @@
2533
2539
  "confirmRemove": {
2534
2540
  "title": "Diese Zugangsdaten entfernen?",
2535
2541
  "body": "\"{name}\" wird entfernt. Dies kann nicht rückgängig gemacht werden."
2536
- }
2542
+ },
2543
+ "defaultBadge": "Standard",
2544
+ "pinDefault": "Als Standard festlegen",
2545
+ "disabledBadge": "Deaktiviert",
2546
+ "enableToggle": "Dieses Token aktivieren oder deaktivieren"
2537
2547
  }
2538
2548
  },
2539
2549
  "github": {
@@ -2979,7 +2979,8 @@
2979
2979
  "toast": {
2980
2980
  "connected": "API key connected",
2981
2981
  "connectFailed": "Could not connect key",
2982
- "removeFailed": "Could not remove key"
2982
+ "removeFailed": "Could not remove key",
2983
+ "updateFailed": "Could not update key"
2983
2984
  },
2984
2985
  "providers": {
2985
2986
  "openai": {
@@ -3018,7 +3019,11 @@
3018
3019
  "step2": "The gateway's base URL is set by your deployment operator (LITELLM_BASE_URL), not here."
3019
3020
  }
3020
3021
  },
3021
- "keyNoun": "this API key"
3022
+ "keyNoun": "this API key",
3023
+ "defaultBadge": "Default",
3024
+ "pinDefault": "Set as default",
3025
+ "disabledBadge": "Disabled",
3026
+ "enableToggle": "Enable or disable this key"
3022
3027
  },
3023
3028
  "vendorCredentials": {
3024
3029
  "title": "LLM Vendors",
@@ -3044,7 +3049,8 @@
3044
3049
  "toast": {
3045
3050
  "connected": "Token connected",
3046
3051
  "connectFailed": "Could not connect token",
3047
- "removeFailed": "Could not remove token"
3052
+ "removeFailed": "Could not remove token",
3053
+ "updateFailed": "Could not update token"
3048
3054
  },
3049
3055
  "vendors": {
3050
3056
  "kimi": {
@@ -3063,7 +3069,11 @@
3063
3069
  "confirmRemove": {
3064
3070
  "title": "Remove this credential?",
3065
3071
  "body": "\"{name}\" will be removed. This can't be undone."
3066
- }
3072
+ },
3073
+ "defaultBadge": "Default",
3074
+ "pinDefault": "Set as default",
3075
+ "disabledBadge": "Disabled",
3076
+ "enableToggle": "Enable or disable this token"
3067
3077
  }
3068
3078
  },
3069
3079
  "github": {