@cat-factory/app 0.311.1 → 0.311.3
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/README.md +70 -5
- package/app/app.config.ts +6 -18
- package/app/components/assistant/AssistantModal.vue +2 -5
- package/app/components/auth/AuthGate.vue +2 -1
- package/app/components/binaryCandidates/BinaryCandidatesWindow.vue +14 -15
- package/app/components/binaryOutput/StoredAssetView.vue +1 -3
- package/app/components/board/AddTaskModal.vue +3 -10
- package/app/components/board/AgentFailureCard.vue +4 -5
- package/app/components/board/AgentStopButton.vue +5 -7
- package/app/components/board/BoardCanvas.vue +31 -30
- package/app/components/board/CreateInitiativeModal.vue +2 -4
- package/app/components/board/RecurringPipelineModal.vue +2 -7
- package/app/components/board/nodes/AgentChip.vue +3 -2
- package/app/components/board/nodes/BlockNode.vue +14 -11
- package/app/components/board/nodes/InitiativeCard.vue +6 -3
- package/app/components/bootstrap/AdoptionReviewModal.vue +4 -7
- package/app/components/bootstrap/BootstrapModal.vue +6 -15
- package/app/components/bootstrap/BootstrapRunSteps.vue +3 -1
- package/app/components/brainstorm/BrainstormWindow.vue +32 -26
- package/app/components/bugFishing/BugFishingWindow.vue +15 -13
- package/app/components/clarity/ClarityReviewWindow.vue +31 -26
- package/app/components/common/AsyncViewError.vue +1 -1
- package/app/components/common/ClarificationItem.vue +3 -1
- package/app/components/common/EmptyState.vue +41 -19
- package/app/components/common/InterviewGateNotice.vue +3 -1
- package/app/components/common/Spinner.vue +28 -0
- package/app/components/consensus/ConsensusSessionWindow.vue +5 -5
- package/app/components/docs/DocInterviewWindow.vue +6 -6
- package/app/components/documents/ContextDocumentPicker.vue +2 -2
- package/app/components/documents/DocumentImportModal.vue +9 -8
- package/app/components/documents/DocumentSourceConnectModal.vue +10 -10
- package/app/components/documents/DocumentTemplatesModal.vue +15 -9
- package/app/components/documents/RepoContextDocPicker.vue +2 -2
- package/app/components/documents/SpawnPreviewModal.vue +4 -6
- package/app/components/documents/StartFromDesignModal.vue +2 -1
- package/app/components/documents/TaskContextDocs.vue +4 -5
- package/app/components/environments/EnvironmentStatusPanel.vue +11 -8
- package/app/components/environments/steps/EnvPickStep.vue +7 -3
- package/app/components/environments/steps/EnvPreflightStep.vue +6 -5
- package/app/components/focus/BlockFocusView.vue +6 -5
- package/app/components/followUp/FollowUpWindow.vue +7 -9
- package/app/components/forkDecision/ForkDecisionWindow.vue +9 -8
- package/app/components/foundational/FoundationalServiceCatalogList.vue +9 -5
- package/app/components/foundational/FoundationalServiceRegistry.vue +11 -7
- package/app/components/foundational/FoundationalServiceSources.vue +20 -22
- package/app/components/foundational/FoundationalSuppressions.vue +2 -2
- package/app/components/foundational/ServiceCatalogConnection.vue +15 -15
- package/app/components/fragments/FragmentLibraryManager.vue +36 -34
- package/app/components/fragments/FragmentSelector.vue +7 -3
- package/app/components/gates/GateResultView.vue +6 -9
- package/app/components/github/AddServiceFromRepoModal.vue +6 -10
- package/app/components/github/GitHubConnect.vue +10 -17
- package/app/components/github/GitHubPanel.vue +44 -47
- package/app/components/github/RepoTreeBrowser.vue +9 -5
- package/app/components/guidedReview/GuidedReviewModal.vue +16 -6
- package/app/components/guidedReview/GuidedReviewOverview.vue +2 -1
- package/app/components/guidedReview/GuidedReviewPicker.vue +10 -7
- package/app/components/guidedReview/GuidedReviewThread.vue +9 -4
- package/app/components/humanTest/HumanTestWindow.vue +2 -1
- package/app/components/initiative/InitiativePlanningWindow.vue +6 -6
- package/app/components/initiative/InitiativeTrackerWindow.vue +11 -11
- package/app/components/judge/JudgeResultView.vue +2 -7
- package/app/components/kaizen/KaizenPanel.vue +20 -5
- package/app/components/kaizen/KaizenStepStatus.vue +2 -1
- package/app/components/layout/AccountAuditLog.vue +11 -3
- package/app/components/layout/AccountDeploymentSettings.vue +17 -62
- package/app/components/layout/AccountFailureKindRules.vue +6 -5
- package/app/components/layout/AccountModelPolicySettings.vue +2 -6
- package/app/components/layout/AccountPlatformAlertSettings.vue +5 -13
- package/app/components/layout/AccountRiskPolicySettings.vue +13 -14
- package/app/components/layout/AccountRunCredentialSettings.vue +2 -2
- package/app/components/layout/AccountTeamSettings.vue +12 -9
- package/app/components/layout/BoardSwitcher.vue +2 -2
- package/app/components/layout/BoardToolbar.vue +3 -11
- package/app/components/layout/CommandBar.vue +7 -3
- package/app/components/layout/ConnectionStatusBanner.vue +2 -1
- package/app/components/layout/IntegrationsHub.vue +7 -3
- package/app/components/layout/ModelProvidersHub.vue +7 -3
- package/app/components/layout/NotificationsInbox.vue +3 -11
- package/app/components/layout/SideBar.vue +1 -1
- package/app/components/layout/WorkspaceMembersSettings.vue +12 -5
- package/app/components/media/ArtifactLightbox.vue +4 -3
- package/app/components/notifications/NotificationSettingsPanel.vue +4 -11
- package/app/components/observability/ToolCallList.vue +12 -17
- package/app/components/panels/AgentStepDetail.vue +5 -5
- package/app/components/panels/GenericStructuredResultView.vue +6 -9
- package/app/components/panels/InspectorPanel.vue +2 -1
- package/app/components/panels/MergerResultView.vue +5 -6
- package/app/components/panels/ObservabilityPanel.vue +26 -41
- package/app/components/panels/OperatorDashboardPanel.vue +29 -17
- package/app/components/panels/ReportsPanel.vue +17 -11
- package/app/components/panels/ReportsSpendBreakdown.vue +7 -3
- package/app/components/panels/ResultWindowShell.vue +1 -1
- package/app/components/panels/RunDetailLoadState.vue +4 -3
- package/app/components/panels/StepContainerStatus.vue +4 -3
- package/app/components/panels/StepDelegatedStatus.vue +3 -2
- package/app/components/panels/StepMetadataCard.vue +6 -8
- package/app/components/panels/StepRunMeta.vue +2 -5
- package/app/components/panels/inspector/ContainerSummary.vue +7 -1
- package/app/components/panels/inspector/EpicChildren.vue +7 -3
- package/app/components/panels/inspector/FrontendConfig.vue +7 -3
- package/app/components/panels/inspector/InitiativeInspector.vue +5 -2
- package/app/components/panels/inspector/ServiceConnections.vue +7 -3
- package/app/components/panels/inspector/ServiceReleaseHealthConfig.vue +2 -6
- package/app/components/panels/inspector/ServiceTestSecrets.vue +2 -6
- package/app/components/panels/inspector/ServiceTestingContext.vue +2 -6
- package/app/components/panels/inspector/ServiceValidationConfig.vue +38 -39
- package/app/components/panels/inspector/TaskExecution.vue +5 -6
- package/app/components/panels/inspector/TaskRunSettings.vue +7 -3
- package/app/components/panels/inspector/TaskStructure.vue +0 -1
- package/app/components/pipeline/AgentPromptEditor.vue +25 -18
- package/app/components/pipeline/PipelineBuilder.vue +14 -24
- package/app/components/pipeline/PipelineProgress.vue +16 -17
- package/app/components/prReview/PrReviewPhaseBadge.vue +3 -2
- package/app/components/prReview/PrReviewWindow.vue +16 -20
- package/app/components/providers/ApiKeysSection.vue +2 -6
- package/app/components/providers/PersonalCredentialModal.vue +2 -6
- package/app/components/providers/PersonalSubscriptionSection.vue +3 -7
- package/app/components/providers/VendorCredentialsModal.vue +2 -6
- package/app/components/provisioning/ProvisioningLogsDrawer.vue +10 -6
- package/app/components/ralph/RalphLoopResultView.vue +2 -7
- package/app/components/requirements/RequirementsReviewWindow.vue +44 -39
- package/app/components/sandbox/SandboxPanel.vue +37 -25
- package/app/components/settings/AccountSettingsPanel.vue +6 -3
- package/app/components/settings/ApiTokensPanel.vue +2 -6
- package/app/components/settings/BudgetSettings.vue +2 -2
- package/app/components/settings/CapabilityCredentialsPanel.vue +2 -6
- package/app/components/settings/CloudflareHandlerSection.vue +3 -7
- package/app/components/settings/ConsensusGroupsSection.vue +7 -3
- package/app/components/settings/CustomManifestTypeEditor.vue +7 -3
- package/app/components/settings/DefaultProvisionTypeSection.vue +2 -2
- package/app/components/settings/InfraHandlersConfigurator.vue +8 -11
- package/app/components/settings/IssueTrackerPanel.vue +9 -9
- package/app/components/settings/LocalContainerPoolSettings.vue +2 -6
- package/app/components/settings/LocalModelEndpointsPanel.vue +5 -7
- package/app/components/settings/McpAuthorizeScreen.vue +8 -3
- package/app/components/settings/McpOAuthCallbackScreen.vue +2 -1
- package/app/components/settings/ModelConfigurationPanel.vue +45 -17
- package/app/components/settings/ObservabilityConnectionPanel.vue +4 -15
- package/app/components/settings/OpenRouterCatalogPanel.vue +8 -24
- package/app/components/settings/PackageRegistriesPanel.vue +2 -6
- package/app/components/settings/ProviderConnectionTab.vue +3 -7
- package/app/components/settings/RiskPolicyPanel.vue +4 -16
- package/app/components/settings/SharedStacksPanel.vue +8 -18
- package/app/components/settings/TaskTypeSuppressionsPanel.vue +10 -6
- package/app/components/settings/UsageSettings.vue +11 -2
- package/app/components/settings/UserSecretsSection.vue +5 -7
- package/app/components/settings/WorkspaceMetadataSettings.vue +9 -9
- package/app/components/settings/WorkspaceSettingsPanel.vue +3 -11
- package/app/components/skills/ReviewSkillQueue.vue +7 -3
- package/app/components/skills/SkillLibraryManager.vue +20 -18
- package/app/components/slack/SlackPanel.vue +5 -8
- package/app/components/spec/ServiceSpecWindow.vue +32 -29
- package/app/components/tasks/BugHuntModal.vue +23 -19
- package/app/components/tasks/ContextIssuePicker.vue +2 -2
- package/app/components/tasks/TaskContextIssues.vue +4 -5
- package/app/components/tasks/TaskImportModal.vue +10 -8
- package/app/components/tasks/TaskSourceConnectModal.vue +11 -11
- package/app/components/testing/TestReportWindow.vue +6 -12
- package/app/components/tutorial/TutorialCatalogue.vue +7 -3
- package/app/components/tutorial/TutorialOverlay.vue +2 -1
- package/app/components/tutorial/TutorialPrompt.vue +7 -3
- package/app/components/vcs/GitLabConnect.vue +2 -6
- package/app/components/visualConfirm/VisualConfirmationWindow.vue +9 -4
- package/app/composables/toastTitleKeys.spec.ts +143 -0
- package/app/composables/useActionToast.spec.ts +71 -0
- package/app/composables/useActionToast.ts +101 -0
- package/app/composables/useBlockDeletion.ts +3 -6
- package/app/composables/useConfirmAction.ts +5 -3
- package/app/composables/useContextLinking.spec.ts +3 -0
- package/app/composables/useContextLinking.ts +11 -16
- package/app/composables/useCopyToClipboard.ts +4 -3
- package/app/composables/useNavContributions.ts +4 -5
- package/app/composables/usePipelineErrorToast.spec.ts +24 -0
- package/app/composables/usePipelineErrorToast.ts +26 -2
- package/app/composables/usePipelineLibraryActions.ts +8 -9
- package/app/pages/index.vue +3 -2
- package/app/stores/board/context.ts +4 -3
- package/app/stores/board/placement.ts +8 -20
- package/app/stores/board/removal.ts +4 -13
- package/app/stores/board.spec.ts +10 -1
- package/app/stores/board.ts +3 -2
- package/app/utils/bootstrapSteps.ts +11 -2
- package/app/utils/pipelineRender.ts +21 -8
- package/i18n/locales/de.json +12 -30
- package/i18n/locales/en.json +12 -30
- package/i18n/locales/es.json +12 -30
- package/i18n/locales/fr.json +12 -30
- package/i18n/locales/he.json +12 -30
- package/i18n/locales/it.json +12 -30
- package/i18n/locales/ja.json +12 -30
- package/i18n/locales/pl.json +12 -30
- package/i18n/locales/tr.json +12 -30
- package/i18n/locales/uk.json +12 -30
- package/package.json +1 -1
|
@@ -17,12 +17,13 @@
|
|
|
17
17
|
import { computed, onMounted, ref, watch } from 'vue'
|
|
18
18
|
import type { TaskSourceKind, TaskSourceState, TrackerKind } from '~/types/domain'
|
|
19
19
|
import TaskSourceCard from '~/components/settings/TaskSourceCard.vue'
|
|
20
|
+
import EmptyState from '~/components/common/EmptyState.vue'
|
|
20
21
|
|
|
21
22
|
const { t } = useI18n()
|
|
22
23
|
const tracker = useTrackerStore()
|
|
23
24
|
const tasks = useTasksStore()
|
|
24
25
|
const ui = useUiStore()
|
|
25
|
-
const
|
|
26
|
+
const actionToast = useActionToast()
|
|
26
27
|
const { present } = usePipelineErrorToast()
|
|
27
28
|
|
|
28
29
|
// --- filing tracker + writeback (one Save, persisted on tracker settings) -----
|
|
@@ -114,11 +115,7 @@ async function save() {
|
|
|
114
115
|
writebackResolveOnMerge: resolveOnMerge.value,
|
|
115
116
|
writebackQuestionsOnPark: questionsOnPark.value,
|
|
116
117
|
})
|
|
117
|
-
toast.
|
|
118
|
-
title: t('settings.issueTracker.toast.saved'),
|
|
119
|
-
icon: 'i-lucide-check',
|
|
120
|
-
color: 'success',
|
|
121
|
-
})
|
|
118
|
+
actionToast.success('settings.issueTracker.toast.saved')
|
|
122
119
|
} catch (e) {
|
|
123
120
|
present(e, 'settings.issueTracker.toast.saveFailed')
|
|
124
121
|
} finally {
|
|
@@ -379,9 +376,12 @@ async function checkSetup(source: TaskSourceKind) {
|
|
|
379
376
|
@remedy="openRemedy(source)"
|
|
380
377
|
/>
|
|
381
378
|
|
|
382
|
-
<
|
|
383
|
-
|
|
384
|
-
|
|
379
|
+
<EmptyState
|
|
380
|
+
v-if="tasks.sources.length === 0"
|
|
381
|
+
compact
|
|
382
|
+
icon="i-lucide-list-todo"
|
|
383
|
+
:title="t('settings.issueTracker.linking.none')"
|
|
384
|
+
/>
|
|
385
385
|
</section>
|
|
386
386
|
|
|
387
387
|
<!-- 3. Writeback ---------------------------------------------------------->
|
|
@@ -13,7 +13,7 @@ import { reactive, ref, watch } from 'vue'
|
|
|
13
13
|
|
|
14
14
|
const { t } = useI18n()
|
|
15
15
|
const store = useLocalSettingsStore()
|
|
16
|
-
const
|
|
16
|
+
const actionToast = useActionToast()
|
|
17
17
|
const { present } = usePipelineErrorToast()
|
|
18
18
|
|
|
19
19
|
const saving = ref(false)
|
|
@@ -60,11 +60,7 @@ async function save() {
|
|
|
60
60
|
},
|
|
61
61
|
checkout: { workspaceRoot: draft.workspaceRoot.trim() || '/workspace', cleanKeep },
|
|
62
62
|
})
|
|
63
|
-
toast.
|
|
64
|
-
title: t('settings.localMode.toast.saved'),
|
|
65
|
-
icon: 'i-lucide-check',
|
|
66
|
-
color: 'success',
|
|
67
|
-
})
|
|
63
|
+
actionToast.success('settings.localMode.toast.saved')
|
|
68
64
|
} catch (e) {
|
|
69
65
|
present(e, 'settings.localMode.toast.saveFailed')
|
|
70
66
|
} finally {
|
|
@@ -23,7 +23,7 @@ import IconButton from '~/components/common/IconButton.vue'
|
|
|
23
23
|
const { t } = useI18n()
|
|
24
24
|
const ui = useUiStore()
|
|
25
25
|
const store = useLocalModelsStore()
|
|
26
|
-
const
|
|
26
|
+
const actionToast = useActionToast()
|
|
27
27
|
const { present } = usePipelineErrorToast()
|
|
28
28
|
const { confirm } = useConfirm()
|
|
29
29
|
|
|
@@ -230,12 +230,10 @@ async function save() {
|
|
|
230
230
|
models: selected.value.map((id) => ({ id, ...modalityOf(imageInput.value[id]) })),
|
|
231
231
|
})
|
|
232
232
|
apiKey.value = ''
|
|
233
|
-
toast.
|
|
234
|
-
|
|
233
|
+
actionToast.success('settings.localModelEndpoints.toast.saved', {
|
|
234
|
+
params: {
|
|
235
235
|
name: LOCAL_RUNNER_LABELS[provider.value],
|
|
236
|
-
}
|
|
237
|
-
icon: 'i-lucide-check',
|
|
238
|
-
color: 'success',
|
|
236
|
+
},
|
|
239
237
|
})
|
|
240
238
|
} catch (e) {
|
|
241
239
|
present(e, 'settings.localModelEndpoints.toast.saveFailed')
|
|
@@ -261,7 +259,7 @@ async function remove(p: LocalRunner) {
|
|
|
261
259
|
// The row is gone from the store, so re-seeding the draft it was showing yields the
|
|
262
260
|
// fresh-runner defaults: the same reset, without a second copy of what a reset means.
|
|
263
261
|
if (provider.value === p) seedDraft(p)
|
|
264
|
-
|
|
262
|
+
actionToast.success('settings.localModelEndpoints.toast.removed')
|
|
265
263
|
} catch (e) {
|
|
266
264
|
present(e, 'settings.localModelEndpoints.toast.removeFailed')
|
|
267
265
|
} finally {
|
|
@@ -145,9 +145,14 @@ function backToApp() {
|
|
|
145
145
|
data-testid="mcp-authorize"
|
|
146
146
|
>
|
|
147
147
|
<div class="w-full max-w-md rounded-xl border border-default bg-default/80 p-8 backdrop-blur">
|
|
148
|
-
<
|
|
149
|
-
<
|
|
150
|
-
|
|
148
|
+
<div v-if="screen === 'loading'" class="flex flex-col items-center">
|
|
149
|
+
<USkeleton class="mb-3 h-10 w-10 rounded-full" />
|
|
150
|
+
<USkeleton class="mb-2 h-6 w-3/4" />
|
|
151
|
+
<USkeleton class="mb-6 h-4 w-full" />
|
|
152
|
+
<USkeleton class="mb-4 h-8 w-full" />
|
|
153
|
+
<USkeleton class="mb-6 h-16 w-full" />
|
|
154
|
+
<USkeleton class="h-8 w-full" />
|
|
155
|
+
</div>
|
|
151
156
|
|
|
152
157
|
<template v-else-if="screen === 'failed'">
|
|
153
158
|
<UIcon name="i-lucide-alert-triangle" class="mx-auto mb-3 h-10 w-10 text-app-error-400" />
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { onMounted, ref } from 'vue'
|
|
3
|
+
import Spinner from '~/components/common/Spinner.vue'
|
|
3
4
|
|
|
4
5
|
// Where a vendor's authorization server sends the operator's browser back to after they approve a
|
|
5
6
|
// remote MCP tool server (`/mcp-oauth-callback?code=…&state=…`).
|
|
@@ -69,7 +70,7 @@ function backToApp() {
|
|
|
69
70
|
class="w-full max-w-sm rounded-xl border border-default bg-default/80 p-8 text-center backdrop-blur"
|
|
70
71
|
>
|
|
71
72
|
<template v-if="state === 'working'">
|
|
72
|
-
<
|
|
73
|
+
<Spinner class="mx-auto mb-3 h-10 w-10 text-primary" />
|
|
73
74
|
<h1 class="mb-1 text-lg font-semibold text-highlighted">
|
|
74
75
|
{{ t('settings.toolServers.oauth.callback.working') }}
|
|
75
76
|
</h1>
|
|
@@ -24,6 +24,7 @@ import ProviderPreferenceEditor from '~/components/settings/ProviderPreferenceEd
|
|
|
24
24
|
import { showOverrideField } from '~/utils/uiMode'
|
|
25
25
|
import SectionLabel from '~/components/common/SectionLabel.vue'
|
|
26
26
|
import IconButton from '~/components/common/IconButton.vue'
|
|
27
|
+
import EmptyState from '~/components/common/EmptyState.vue'
|
|
27
28
|
import { FULL_BLEED_TAKEOVER_CLASS } from '~/utils/safeArea'
|
|
28
29
|
|
|
29
30
|
const { t } = useI18n()
|
|
@@ -36,7 +37,7 @@ const agentTier = useAgentTierStore()
|
|
|
36
37
|
const creds = useVendorCredentialsStore()
|
|
37
38
|
const workspace = useWorkspaceStore()
|
|
38
39
|
const { present } = usePipelineErrorToast()
|
|
39
|
-
const
|
|
40
|
+
const actionToast = useActionToast()
|
|
40
41
|
const { confirm } = useConfirm()
|
|
41
42
|
|
|
42
43
|
const open = computed({
|
|
@@ -93,13 +94,25 @@ const hiddenByTier = computed(() =>
|
|
|
93
94
|
filter.value.trim() ? 0 : configurableKinds.value.length - tieredKinds.value.length,
|
|
94
95
|
)
|
|
95
96
|
|
|
97
|
+
// Set when the catalog read rejects, so the preset list states the failure instead of holding a
|
|
98
|
+
// skeleton that never resolves (`models.loaded` stays false after a failed read).
|
|
99
|
+
const catalogFailed = ref(false)
|
|
100
|
+
|
|
101
|
+
function loadCatalog() {
|
|
102
|
+
catalogFailed.value = false
|
|
103
|
+
models.ensureLoaded(workspace.workspaceId ?? undefined).catch((error) => {
|
|
104
|
+
catalogFailed.value = true
|
|
105
|
+
present(error, 'settings.modelConfiguration.toast.catalogFailed')
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
96
109
|
watch(
|
|
97
110
|
open,
|
|
98
111
|
(isOpen) => {
|
|
99
112
|
if (isOpen) {
|
|
100
113
|
editor.value = null
|
|
101
114
|
filter.value = ''
|
|
102
|
-
|
|
115
|
+
loadCatalog()
|
|
103
116
|
if (workspace.workspaceId) void creds.load(workspace.workspaceId)
|
|
104
117
|
}
|
|
105
118
|
},
|
|
@@ -258,11 +271,8 @@ async function save() {
|
|
|
258
271
|
// NOT through `present`: that funnel classifies a BACKEND failure, and a synthesized local
|
|
259
272
|
// `Error` carries no envelope and no status, so it lands on the network-fault description and
|
|
260
273
|
// tells the user the server could not be reached about a check that never left the browser.
|
|
261
|
-
toast.
|
|
262
|
-
title: t('settings.modelConfiguration.toast.nameRequiredTitle'),
|
|
274
|
+
actionToast.error('settings.modelConfiguration.toast.nameRequiredTitle', {
|
|
263
275
|
description: t('settings.modelConfiguration.toast.nameRequiredBody'),
|
|
264
|
-
color: 'warning',
|
|
265
|
-
icon: 'i-lucide-triangle-alert',
|
|
266
276
|
})
|
|
267
277
|
return
|
|
268
278
|
}
|
|
@@ -372,9 +382,25 @@ async function save() {
|
|
|
372
382
|
</UButton>
|
|
373
383
|
</div>
|
|
374
384
|
|
|
375
|
-
<
|
|
376
|
-
|
|
377
|
-
|
|
385
|
+
<UAlert
|
|
386
|
+
v-if="catalogFailed"
|
|
387
|
+
color="error"
|
|
388
|
+
variant="subtle"
|
|
389
|
+
icon="i-lucide-triangle-alert"
|
|
390
|
+
:title="t('settings.modelConfiguration.list.catalogFailed')"
|
|
391
|
+
:actions="[
|
|
392
|
+
{
|
|
393
|
+
label: t('common.retry'),
|
|
394
|
+
color: 'neutral',
|
|
395
|
+
variant: 'outline',
|
|
396
|
+
onClick: loadCatalog,
|
|
397
|
+
},
|
|
398
|
+
]"
|
|
399
|
+
/>
|
|
400
|
+
|
|
401
|
+
<div v-else-if="!models.loaded" class="space-y-3">
|
|
402
|
+
<USkeleton v-for="n in 3" :key="n" class="h-20 w-full rounded-lg" />
|
|
403
|
+
</div>
|
|
378
404
|
|
|
379
405
|
<div v-else class="space-y-3">
|
|
380
406
|
<div
|
|
@@ -443,9 +469,11 @@ async function save() {
|
|
|
443
469
|
</span>
|
|
444
470
|
</div>
|
|
445
471
|
</div>
|
|
446
|
-
<
|
|
447
|
-
|
|
448
|
-
|
|
472
|
+
<EmptyState
|
|
473
|
+
v-if="sortedPresets.length === 0"
|
|
474
|
+
icon="i-lucide-sliders-horizontal"
|
|
475
|
+
:title="t('settings.modelConfiguration.list.empty')"
|
|
476
|
+
/>
|
|
449
477
|
</div>
|
|
450
478
|
|
|
451
479
|
<!-- The consensus-GROUP library: which model PANELS review the workspace's heavier
|
|
@@ -549,12 +577,12 @@ async function save() {
|
|
|
549
577
|
</UButton>
|
|
550
578
|
</UDropdownMenu>
|
|
551
579
|
</div>
|
|
552
|
-
<
|
|
580
|
+
<EmptyState
|
|
553
581
|
v-if="filteredKinds.length === 0"
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
582
|
+
compact
|
|
583
|
+
icon="i-lucide-search-x"
|
|
584
|
+
:title="t('settings.modelConfiguration.editor.noAgentsMatch', { filter })"
|
|
585
|
+
/>
|
|
558
586
|
</div>
|
|
559
587
|
</div>
|
|
560
588
|
|
|
@@ -12,7 +12,7 @@ import SecretInput from '~/components/common/SecretInput.vue'
|
|
|
12
12
|
const { t } = useI18n()
|
|
13
13
|
const ui = useUiStore()
|
|
14
14
|
const store = useReleaseHealthStore()
|
|
15
|
-
const
|
|
15
|
+
const actionToast = useActionToast()
|
|
16
16
|
const { present } = usePipelineErrorToast()
|
|
17
17
|
const { confirmAction, toastDone } = useConfirmAction()
|
|
18
18
|
|
|
@@ -66,21 +66,14 @@ async function saveIncident() {
|
|
|
66
66
|
}
|
|
67
67
|
if (incidentIo.apiKey.trim()) input.incidentIo = { apiKey: incidentIo.apiKey.trim() }
|
|
68
68
|
if (!input.pagerDuty && !input.incidentIo) {
|
|
69
|
-
toast.
|
|
70
|
-
title: t('settings.observabilityConnection.toast.incidentCredsRequired'),
|
|
71
|
-
color: 'error',
|
|
72
|
-
})
|
|
69
|
+
actionToast.error('settings.observabilityConnection.toast.incidentCredsRequired')
|
|
73
70
|
return
|
|
74
71
|
}
|
|
75
72
|
await store.saveIncident(input)
|
|
76
73
|
pagerDuty.apiToken = ''
|
|
77
74
|
pagerDuty.fromEmail = ''
|
|
78
75
|
incidentIo.apiKey = ''
|
|
79
|
-
toast.
|
|
80
|
-
title: t('settings.observabilityConnection.toast.incidentSaved'),
|
|
81
|
-
icon: 'i-lucide-check',
|
|
82
|
-
color: 'success',
|
|
83
|
-
})
|
|
76
|
+
actionToast.success('settings.observabilityConnection.toast.incidentSaved')
|
|
84
77
|
} catch (e) {
|
|
85
78
|
present(e, 'settings.observabilityConnection.toast.incidentSaveFailed')
|
|
86
79
|
} finally {
|
|
@@ -111,11 +104,7 @@ async function saveConnection() {
|
|
|
111
104
|
})
|
|
112
105
|
datadog.apiKey = ''
|
|
113
106
|
datadog.appKey = ''
|
|
114
|
-
toast.
|
|
115
|
-
title: t('settings.observabilityConnection.toast.connected'),
|
|
116
|
-
icon: 'i-lucide-check',
|
|
117
|
-
color: 'success',
|
|
118
|
-
})
|
|
107
|
+
actionToast.success('settings.observabilityConnection.toast.connected')
|
|
119
108
|
} catch (e) {
|
|
120
109
|
present(e, 'settings.observabilityConnection.toast.connectFailed')
|
|
121
110
|
} finally {
|
|
@@ -19,8 +19,8 @@ const workspace = useWorkspaceStore()
|
|
|
19
19
|
const store = useOpenRouterStore()
|
|
20
20
|
const apiKeys = useApiKeysStore()
|
|
21
21
|
const models = useModelsStore()
|
|
22
|
-
const
|
|
23
|
-
const { present } = usePipelineErrorToast()
|
|
22
|
+
const actionToast = useActionToast()
|
|
23
|
+
const { present, presentReported } = usePipelineErrorToast()
|
|
24
24
|
|
|
25
25
|
const open = computed({
|
|
26
26
|
get: () => ui.openRouterOpen,
|
|
@@ -155,21 +155,14 @@ async function connectKey() {
|
|
|
155
155
|
if (scope === 'workspace') await apiKeys.removeWorkspaceKey(created.id).catch(() => {})
|
|
156
156
|
else await apiKeys.removeUserKey(created.id).catch(() => {})
|
|
157
157
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
icon: 'i-lucide-triangle-alert',
|
|
162
|
-
color: 'error',
|
|
163
|
-
})
|
|
158
|
+
// `!reachable` covers an outage or a network fault as well as a refused key, and the probe
|
|
159
|
+
// does not say which, so the description stays the generic "it reported a failure" line.
|
|
160
|
+
presentReported(store.refreshError, 'settings.openRouterCatalog.toast.connectFailed')
|
|
164
161
|
return
|
|
165
162
|
}
|
|
166
163
|
keyValue.value = ''
|
|
167
164
|
keyLabel.value = ''
|
|
168
|
-
toast.
|
|
169
|
-
title: t('settings.openRouterCatalog.toast.connected'),
|
|
170
|
-
icon: 'i-lucide-check',
|
|
171
|
-
color: 'success',
|
|
172
|
-
})
|
|
165
|
+
actionToast.success('settings.openRouterCatalog.toast.connected')
|
|
173
166
|
} catch (e) {
|
|
174
167
|
present(e, 'settings.openRouterCatalog.toast.connectFailed')
|
|
175
168
|
} finally {
|
|
@@ -181,12 +174,7 @@ async function refresh() {
|
|
|
181
174
|
if (!workspace.workspaceId) return
|
|
182
175
|
const result = await store.refresh(workspace.workspaceId)
|
|
183
176
|
if (!result.reachable) {
|
|
184
|
-
toast.
|
|
185
|
-
title: t('settings.openRouterCatalog.toast.unreachable'),
|
|
186
|
-
description: store.refreshError ?? t('settings.openRouterCatalog.toast.connectFirst'),
|
|
187
|
-
icon: 'i-lucide-triangle-alert',
|
|
188
|
-
color: 'error',
|
|
189
|
-
})
|
|
177
|
+
presentReported(store.refreshError, 'settings.openRouterCatalog.toast.unreachable')
|
|
190
178
|
}
|
|
191
179
|
}
|
|
192
180
|
|
|
@@ -202,11 +190,7 @@ async function save() {
|
|
|
202
190
|
await store.save(workspace.workspaceId, models2)
|
|
203
191
|
// Reflect newly-enabled models in the picker immediately.
|
|
204
192
|
await models.refresh(workspace.workspaceId)
|
|
205
|
-
toast.
|
|
206
|
-
title: t('settings.openRouterCatalog.toast.saved'),
|
|
207
|
-
icon: 'i-lucide-check',
|
|
208
|
-
color: 'success',
|
|
209
|
-
})
|
|
193
|
+
actionToast.success('settings.openRouterCatalog.toast.saved')
|
|
210
194
|
} catch (e) {
|
|
211
195
|
present(e, 'settings.openRouterCatalog.toast.saveFailed')
|
|
212
196
|
} finally {
|
|
@@ -12,7 +12,7 @@ import SecretInput from '~/components/common/SecretInput.vue'
|
|
|
12
12
|
|
|
13
13
|
const { t } = useI18n()
|
|
14
14
|
const store = usePackageRegistriesStore()
|
|
15
|
-
const
|
|
15
|
+
const actionToast = useActionToast()
|
|
16
16
|
const { present } = usePipelineErrorToast()
|
|
17
17
|
const { confirmAction, toastDone } = useConfirmAction()
|
|
18
18
|
|
|
@@ -70,11 +70,7 @@ async function addEntry() {
|
|
|
70
70
|
})
|
|
71
71
|
form.scopes = ''
|
|
72
72
|
form.token = ''
|
|
73
|
-
toast.
|
|
74
|
-
title: t('settings.packageRegistries.toast.added'),
|
|
75
|
-
icon: 'i-lucide-check',
|
|
76
|
-
color: 'success',
|
|
77
|
-
})
|
|
73
|
+
actionToast.success('settings.packageRegistries.toast.added')
|
|
78
74
|
} catch (e) {
|
|
79
75
|
present(e, 'settings.packageRegistries.toast.addFailed')
|
|
80
76
|
} finally {
|
|
@@ -36,7 +36,7 @@ const emit = defineEmits<{ connected: [] }>()
|
|
|
36
36
|
|
|
37
37
|
const { t } = useI18n()
|
|
38
38
|
const store = useProviderConnectionsStore()
|
|
39
|
-
const
|
|
39
|
+
const actionToast = useActionToast()
|
|
40
40
|
const { present } = usePipelineErrorToast()
|
|
41
41
|
const { confirmAction } = useConfirmAction()
|
|
42
42
|
|
|
@@ -208,11 +208,7 @@ function buildFlatPayload() {
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
function toastSaved() {
|
|
211
|
-
toast.
|
|
212
|
-
title: t('settings.providerConnection.toast.saved', { title: title.value }),
|
|
213
|
-
icon: 'i-lucide-check',
|
|
214
|
-
color: 'success',
|
|
215
|
-
})
|
|
211
|
+
actionToast.success('settings.providerConnection.toast.saved', { params: { title: title.value } })
|
|
216
212
|
}
|
|
217
213
|
|
|
218
214
|
// --- Native flat-form actions (both manifest-overlay and config-overlay flavours) ---
|
|
@@ -330,7 +326,7 @@ async function remove() {
|
|
|
330
326
|
try {
|
|
331
327
|
await store.remove(props.kind)
|
|
332
328
|
resetDraft()
|
|
333
|
-
|
|
329
|
+
actionToast.success('settings.providerConnection.toast.removed')
|
|
334
330
|
} catch (e) {
|
|
335
331
|
present(e, 'settings.providerConnection.toast.removeFailed')
|
|
336
332
|
} finally {
|
|
@@ -18,7 +18,7 @@ import SectionLabel from '~/components/common/SectionLabel.vue'
|
|
|
18
18
|
const { t } = useI18n()
|
|
19
19
|
|
|
20
20
|
const store = useRiskPoliciesStore()
|
|
21
|
-
const
|
|
21
|
+
const actionToast = useActionToast()
|
|
22
22
|
const { present } = usePipelineErrorToast()
|
|
23
23
|
const { confirm } = useConfirm()
|
|
24
24
|
|
|
@@ -69,11 +69,7 @@ async function run(key: string, titleKey: string, action: () => Promise<unknown>
|
|
|
69
69
|
async function save(policy: RiskPolicyLibraryEntry, patch: UpdateRiskPolicyInput) {
|
|
70
70
|
await run(policy.id, 'settings.riskPolicy.toast.saveFailed', async () => {
|
|
71
71
|
await store.update(policy.id, patch)
|
|
72
|
-
toast.
|
|
73
|
-
title: t('settings.riskPolicy.toast.saved'),
|
|
74
|
-
icon: 'i-lucide-check',
|
|
75
|
-
color: 'success',
|
|
76
|
-
})
|
|
72
|
+
actionToast.success('settings.riskPolicy.toast.saved')
|
|
77
73
|
})
|
|
78
74
|
}
|
|
79
75
|
|
|
@@ -112,11 +108,7 @@ async function create(input: Parameters<typeof store.create>[0]) {
|
|
|
112
108
|
creating.value = true
|
|
113
109
|
try {
|
|
114
110
|
await store.create(input)
|
|
115
|
-
toast.
|
|
116
|
-
title: t('settings.riskPolicy.toast.created'),
|
|
117
|
-
icon: 'i-lucide-check',
|
|
118
|
-
color: 'success',
|
|
119
|
-
})
|
|
111
|
+
actionToast.success('settings.riskPolicy.toast.created')
|
|
120
112
|
} catch (e) {
|
|
121
113
|
present(e, 'settings.riskPolicy.toast.createFailed')
|
|
122
114
|
} finally {
|
|
@@ -136,11 +128,7 @@ function clone(policy: RiskPolicyLibraryEntry) {
|
|
|
136
128
|
t('settings.riskPolicy.inherited.copyName', { name: source }),
|
|
137
129
|
)
|
|
138
130
|
await store.clone(policy.id, name)
|
|
139
|
-
toast.
|
|
140
|
-
title: t('settings.riskPolicy.toast.cloned'),
|
|
141
|
-
icon: 'i-lucide-check',
|
|
142
|
-
color: 'success',
|
|
143
|
-
})
|
|
131
|
+
actionToast.success('settings.riskPolicy.toast.cloned')
|
|
144
132
|
})
|
|
145
133
|
}
|
|
146
134
|
|
|
@@ -15,8 +15,8 @@ import type {
|
|
|
15
15
|
|
|
16
16
|
const { t } = useI18n()
|
|
17
17
|
const store = useSharedStacksStore()
|
|
18
|
-
const
|
|
19
|
-
const { present } = usePipelineErrorToast()
|
|
18
|
+
const actionToast = useActionToast()
|
|
19
|
+
const { present, presentReported } = usePipelineErrorToast()
|
|
20
20
|
const { confirmAction, toastDone } = useConfirmAction()
|
|
21
21
|
|
|
22
22
|
const stacks = computed(() => store.stacks)
|
|
@@ -150,11 +150,8 @@ async function autodetect() {
|
|
|
150
150
|
...(form.directory.trim() ? { directory: form.directory.trim() } : {}),
|
|
151
151
|
})
|
|
152
152
|
if (!rec.detected) {
|
|
153
|
-
|
|
154
|
-
title: t('settings.sharedStacks.detect.nothing'),
|
|
153
|
+
actionToast.warning('settings.sharedStacks.detect.nothing', {
|
|
155
154
|
description: rec.notes[0]?.message ?? '',
|
|
156
|
-
icon: 'i-lucide-info',
|
|
157
|
-
color: 'warning',
|
|
158
155
|
})
|
|
159
156
|
return
|
|
160
157
|
}
|
|
@@ -163,11 +160,8 @@ async function autodetect() {
|
|
|
163
160
|
form.composeProfiles = rec.composeProfiles.join(', ')
|
|
164
161
|
form.managedNetworks = rec.managedNetworks.join(', ')
|
|
165
162
|
detectedEnvFiles.value = rec.envFiles
|
|
166
|
-
|
|
167
|
-
title: t('settings.sharedStacks.detect.detected'),
|
|
163
|
+
actionToast.success('settings.sharedStacks.detect.detected', {
|
|
168
164
|
description: t('settings.sharedStacks.detect.detectedBody'),
|
|
169
|
-
icon: 'i-lucide-wand-sparkles',
|
|
170
|
-
color: 'success',
|
|
171
165
|
})
|
|
172
166
|
} catch (e) {
|
|
173
167
|
present(e, 'settings.sharedStacks.detect.failed')
|
|
@@ -207,13 +201,9 @@ async function saveStack() {
|
|
|
207
201
|
await store.create(payload)
|
|
208
202
|
}
|
|
209
203
|
resetForm()
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
),
|
|
214
|
-
icon: 'i-lucide-check',
|
|
215
|
-
color: 'success',
|
|
216
|
-
})
|
|
204
|
+
actionToast.success(
|
|
205
|
+
editing ? 'settings.sharedStacks.toast.updated' : 'settings.sharedStacks.toast.created',
|
|
206
|
+
)
|
|
217
207
|
} catch (e) {
|
|
218
208
|
present(
|
|
219
209
|
e,
|
|
@@ -233,7 +223,7 @@ async function start(stack: SharedStack) {
|
|
|
233
223
|
// surface that as an error toast too — not only a thrown transport/unavailable error.
|
|
234
224
|
const updated = await store.ensureUp(stack.id)
|
|
235
225
|
if (updated.status === 'failed') {
|
|
236
|
-
|
|
226
|
+
presentReported(updated.lastError, 'settings.sharedStacks.toast.startFailed')
|
|
237
227
|
}
|
|
238
228
|
} catch (e) {
|
|
239
229
|
present(e, 'settings.sharedStacks.toast.startFailed')
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
// convention); only the chrome around them is i18n.
|
|
13
13
|
import { onMounted, ref } from 'vue'
|
|
14
14
|
import type { TaskTypeSuppression } from '~/types/domain'
|
|
15
|
+
import EmptyState from '~/components/common/EmptyState.vue'
|
|
15
16
|
|
|
16
17
|
const { t } = useI18n()
|
|
17
18
|
const api = useApi()
|
|
@@ -65,12 +66,15 @@ async function toggle(row: TaskTypeSuppression, offered: boolean) {
|
|
|
65
66
|
{{ t('settings.taskTypeSuppressions.intro') }}
|
|
66
67
|
</p>
|
|
67
68
|
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
</
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
<div v-if="loading" class="space-y-2">
|
|
70
|
+
<USkeleton v-for="n in 3" :key="n" class="h-12 w-full" />
|
|
71
|
+
</div>
|
|
72
|
+
<EmptyState
|
|
73
|
+
v-else-if="!rows.length"
|
|
74
|
+
compact
|
|
75
|
+
icon="i-lucide-list-checks"
|
|
76
|
+
:title="t('settings.taskTypeSuppressions.empty')"
|
|
77
|
+
/>
|
|
74
78
|
<ul v-else class="space-y-2" data-testid="task-type-suppressions">
|
|
75
79
|
<li
|
|
76
80
|
v-for="row in rows"
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// only the metered rows. See docs/initiatives/usage-and-quota-tracking.md.
|
|
6
6
|
import { computed, watch } from 'vue'
|
|
7
7
|
import type { UsageBreakdownRow } from '@cat-factory/contracts'
|
|
8
|
+
import EmptyState from '~/components/common/EmptyState.vue'
|
|
8
9
|
|
|
9
10
|
const { t, n, d } = useI18n()
|
|
10
11
|
const usage = useUsageStore()
|
|
@@ -46,9 +47,17 @@ const hasAny = computed(() => usage.rows.length > 0)
|
|
|
46
47
|
{{ t('settings.usage.period', { date: d(new Date(usage.report.periodStart), 'short') }) }}
|
|
47
48
|
</p>
|
|
48
49
|
|
|
49
|
-
<
|
|
50
|
+
<div v-if="usage.loading" class="space-y-3">
|
|
51
|
+
<USkeleton class="h-5 w-1/3" />
|
|
52
|
+
<USkeleton v-for="n in 3" :key="n" class="h-8 w-full" />
|
|
53
|
+
</div>
|
|
50
54
|
<p v-else-if="usage.error" class="text-2xs text-app-error-400">{{ usage.error }}</p>
|
|
51
|
-
<
|
|
55
|
+
<EmptyState
|
|
56
|
+
v-else-if="!hasAny"
|
|
57
|
+
compact
|
|
58
|
+
icon="i-lucide-chart-bar"
|
|
59
|
+
:title="t('settings.usage.empty')"
|
|
60
|
+
/>
|
|
52
61
|
|
|
53
62
|
<template v-else>
|
|
54
63
|
<!-- Subscriptions (flat-rate quota harnesses) -->
|
|
@@ -15,7 +15,7 @@ import SectionLabel from '~/components/common/SectionLabel.vue'
|
|
|
15
15
|
const { t } = useI18n()
|
|
16
16
|
const ui = useUiStore()
|
|
17
17
|
const store = useUserSecretsStore()
|
|
18
|
-
const
|
|
18
|
+
const actionToast = useActionToast()
|
|
19
19
|
const { present } = usePipelineErrorToast()
|
|
20
20
|
const { confirm } = useConfirm()
|
|
21
21
|
|
|
@@ -105,12 +105,10 @@ async function save() {
|
|
|
105
105
|
// (backend/docs/security-model.md). Best-effort: the save already succeeded, so a probe
|
|
106
106
|
// failure must not read as one.
|
|
107
107
|
testResult.value = await store.test(kind.value, payload).catch(() => null)
|
|
108
|
-
toast.
|
|
109
|
-
|
|
108
|
+
actionToast.success('settings.userSecrets.toast.saved', {
|
|
109
|
+
params: {
|
|
110
110
|
label: descriptor.value?.label ?? t('settings.userSecrets.secretFallback'),
|
|
111
|
-
}
|
|
112
|
-
icon: 'i-lucide-check',
|
|
113
|
-
color: 'success',
|
|
111
|
+
},
|
|
114
112
|
})
|
|
115
113
|
} catch (e) {
|
|
116
114
|
present(e, 'settings.userSecrets.toast.saveFailed')
|
|
@@ -134,7 +132,7 @@ async function remove() {
|
|
|
134
132
|
try {
|
|
135
133
|
await store.remove(kind.value)
|
|
136
134
|
resetDraft()
|
|
137
|
-
|
|
135
|
+
actionToast.success('settings.userSecrets.toast.removed')
|
|
138
136
|
} catch (e) {
|
|
139
137
|
present(e, 'settings.userSecrets.toast.removeFailed')
|
|
140
138
|
} finally {
|
|
@@ -18,11 +18,12 @@ import {
|
|
|
18
18
|
import type { WorkspaceMetadataFieldDefinition } from '~/modular/workspace-metadata'
|
|
19
19
|
import type { AppSlots } from '~/modular/slots'
|
|
20
20
|
import SectionLabel from '~/components/common/SectionLabel.vue'
|
|
21
|
+
import EmptyState from '~/components/common/EmptyState.vue'
|
|
21
22
|
|
|
22
23
|
const { t } = useI18n()
|
|
23
24
|
const slots = useReactiveSlots<AppSlots>()
|
|
24
25
|
const store = useWorkspaceSettingsStore()
|
|
25
|
-
const
|
|
26
|
+
const actionToast = useActionToast()
|
|
26
27
|
const { present } = usePipelineErrorToast()
|
|
27
28
|
|
|
28
29
|
/**
|
|
@@ -67,11 +68,7 @@ async function save() {
|
|
|
67
68
|
await store.update({
|
|
68
69
|
metadata: metadataPatchFrom(fields.value, draft, store.settings.metadata),
|
|
69
70
|
})
|
|
70
|
-
toast.
|
|
71
|
-
title: t('settings.workspaceSettings.toast.saved'),
|
|
72
|
-
icon: 'i-lucide-check',
|
|
73
|
-
color: 'success',
|
|
74
|
-
})
|
|
71
|
+
actionToast.success('settings.workspaceSettings.toast.saved')
|
|
75
72
|
} catch (e) {
|
|
76
73
|
present(e, 'settings.workspaceSettings.toast.saveFailed')
|
|
77
74
|
} finally {
|
|
@@ -99,9 +96,12 @@ function selectItems(field: WorkspaceMetadataFieldDefinition) {
|
|
|
99
96
|
</p>
|
|
100
97
|
</section>
|
|
101
98
|
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
99
|
+
<EmptyState
|
|
100
|
+
v-if="fields.length === 0"
|
|
101
|
+
compact
|
|
102
|
+
icon="i-lucide-tags"
|
|
103
|
+
:title="t('settings.workspaceSettings.metadata.empty')"
|
|
104
|
+
/>
|
|
105
105
|
|
|
106
106
|
<template v-else>
|
|
107
107
|
<div class="space-y-4">
|