@cat-factory/app 0.98.0 → 0.99.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/settings/BudgetSettings.vue +294 -0
- package/app/components/settings/WorkspaceSettingsPanel.vue +3 -94
- package/app/composables/api/userSettings.ts +12 -0
- package/app/composables/useApi.ts +2 -0
- package/app/stores/accounts.ts +12 -0
- package/app/stores/userSettings.ts +28 -0
- package/app/stores/workspace.ts +21 -2
- package/app/types/domain.ts +3 -0
- package/i18n/locales/en.json +13 -2
- package/i18n/locales/es.json +13 -2
- package/i18n/locales/fr.json +13 -2
- package/i18n/locales/he.json +13 -2
- package/i18n/locales/ja.json +13 -2
- package/i18n/locales/pl.json +13 -2
- package/i18n/locales/tr.json +13 -2
- package/i18n/locales/uk.json +13 -2
- package/package.json +2 -2
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// The Budget configuration screen: the three spend-budget tiers (workspace, account,
|
|
3
|
+
// user). The workspace tier is a per-workspace monthly limit + currency; the account and
|
|
4
|
+
// user tiers are monthly ceilings that gate a run when EITHER is exhausted. When the
|
|
5
|
+
// operator sets a hard cap env var, the account/user input cannot exceed it and the cap is
|
|
6
|
+
// shown here. See docs/initiatives/tiered-budgets.md.
|
|
7
|
+
import { computed, reactive, ref, watch, type Ref } from 'vue'
|
|
8
|
+
|
|
9
|
+
const { t, n } = useI18n()
|
|
10
|
+
const toast = useToast()
|
|
11
|
+
|
|
12
|
+
const settingsStore = useWorkspaceSettingsStore()
|
|
13
|
+
const userSettingsStore = useUserSettingsStore()
|
|
14
|
+
const accounts = useAccountsStore()
|
|
15
|
+
const workspace = useWorkspaceStore()
|
|
16
|
+
|
|
17
|
+
const caps = computed(() => workspace.budgetCaps)
|
|
18
|
+
const capCurrency = computed(() => caps.value?.currency ?? 'EUR')
|
|
19
|
+
// Format an amount in a given currency. The account/user tiers are in the base pricing
|
|
20
|
+
// currency (`capCurrency`); the workspace tier is in its OWN overridden `spend.currency`, so
|
|
21
|
+
// its callers pass that in — otherwise a USD workspace on a EUR deployment renders `€` on USD.
|
|
22
|
+
const money = (value: number, currency: string = capCurrency.value) =>
|
|
23
|
+
n(value, { key: 'currency', currency })
|
|
24
|
+
|
|
25
|
+
// Persist one tier's budget: save, toast success, then best-effort refresh the snapshot AFTER
|
|
26
|
+
// the save has succeeded. A transient snapshot-refresh failure must NOT report a persisted
|
|
27
|
+
// budget as failed (the spend meter also catches up on the next pushed snapshot); a genuine
|
|
28
|
+
// save rejection surfaces its message so the user sees why (e.g. the operator hard-cap reject).
|
|
29
|
+
async function runSave(saving: Ref<boolean>, save: () => Promise<unknown>) {
|
|
30
|
+
saving.value = true
|
|
31
|
+
try {
|
|
32
|
+
await save()
|
|
33
|
+
toast.add({ title: t('settings.workspaceSettings.toast.budgetSaved'), color: 'success' })
|
|
34
|
+
try {
|
|
35
|
+
await useWorkspaceStore().refresh()
|
|
36
|
+
} catch {
|
|
37
|
+
// ignore — the budget is persisted; the meter will catch up on the next snapshot.
|
|
38
|
+
}
|
|
39
|
+
} catch (e) {
|
|
40
|
+
toast.add({
|
|
41
|
+
title: t('settings.workspaceSettings.toast.budgetSaveFailed'),
|
|
42
|
+
description: e instanceof Error ? e.message : String(e),
|
|
43
|
+
color: 'error',
|
|
44
|
+
})
|
|
45
|
+
} finally {
|
|
46
|
+
saving.value = false
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ---- Workspace tier -------------------------------------------------------
|
|
51
|
+
const wsDraft = reactive({ spendCurrency: '', spendMonthlyLimit: '' })
|
|
52
|
+
function hydrateWorkspace() {
|
|
53
|
+
const s = settingsStore.settings
|
|
54
|
+
wsDraft.spendCurrency = s.spendCurrency ?? ''
|
|
55
|
+
wsDraft.spendMonthlyLimit = s.spendMonthlyLimit == null ? '' : String(s.spendMonthlyLimit)
|
|
56
|
+
}
|
|
57
|
+
watch(() => settingsStore.settings, hydrateWorkspace, { immediate: true })
|
|
58
|
+
|
|
59
|
+
const savingWorkspace = ref(false)
|
|
60
|
+
function saveWorkspace() {
|
|
61
|
+
const raw = String(wsDraft.spendMonthlyLimit ?? '').trim()
|
|
62
|
+
return runSave(savingWorkspace, () =>
|
|
63
|
+
settingsStore.update({
|
|
64
|
+
spendCurrency: wsDraft.spendCurrency.trim()
|
|
65
|
+
? wsDraft.spendCurrency.trim().toUpperCase()
|
|
66
|
+
: null,
|
|
67
|
+
spendMonthlyLimit: raw === '' ? null : Number(raw),
|
|
68
|
+
}),
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---- Account tier ---------------------------------------------------------
|
|
73
|
+
const account = computed(() => accounts.activeAccount)
|
|
74
|
+
const canEditAccount = computed(() => account.value?.roles?.includes('admin') ?? false)
|
|
75
|
+
const accountCap = computed(() => caps.value?.accountMonthlyLimitMax ?? null)
|
|
76
|
+
const accountDraft = ref('')
|
|
77
|
+
watch(
|
|
78
|
+
account,
|
|
79
|
+
(a) => {
|
|
80
|
+
accountDraft.value = a?.spendMonthlyLimit == null ? '' : String(a.spendMonthlyLimit)
|
|
81
|
+
},
|
|
82
|
+
{ immediate: true },
|
|
83
|
+
)
|
|
84
|
+
const accountOverCap = computed(
|
|
85
|
+
() =>
|
|
86
|
+
accountCap.value != null &&
|
|
87
|
+
accountDraft.value.trim() !== '' &&
|
|
88
|
+
Number(accountDraft.value) > accountCap.value,
|
|
89
|
+
)
|
|
90
|
+
const savingAccount = ref(false)
|
|
91
|
+
function saveAccount() {
|
|
92
|
+
const acc = account.value
|
|
93
|
+
if (!acc || accountOverCap.value) return
|
|
94
|
+
const raw = accountDraft.value.trim()
|
|
95
|
+
return runSave(savingAccount, () =>
|
|
96
|
+
accounts.setSpendMonthlyLimit(acc.id, raw === '' ? null : Number(raw)),
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ---- User tier ------------------------------------------------------------
|
|
101
|
+
const userCap = computed(() => caps.value?.userMonthlyLimitMax ?? null)
|
|
102
|
+
const userDraft = ref('')
|
|
103
|
+
watch(
|
|
104
|
+
() => userSettingsStore.settings,
|
|
105
|
+
(s) => {
|
|
106
|
+
userDraft.value = s.spendMonthlyLimit == null ? '' : String(s.spendMonthlyLimit)
|
|
107
|
+
},
|
|
108
|
+
{ immediate: true },
|
|
109
|
+
)
|
|
110
|
+
const userOverCap = computed(
|
|
111
|
+
() =>
|
|
112
|
+
userCap.value != null &&
|
|
113
|
+
userDraft.value.trim() !== '' &&
|
|
114
|
+
Number(userDraft.value) > userCap.value,
|
|
115
|
+
)
|
|
116
|
+
const savingUser = ref(false)
|
|
117
|
+
function saveUser() {
|
|
118
|
+
if (userOverCap.value) return
|
|
119
|
+
const raw = userDraft.value.trim()
|
|
120
|
+
return runSave(savingUser, () =>
|
|
121
|
+
userSettingsStore.update({ spendMonthlyLimit: raw === '' ? null : Number(raw) }),
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
</script>
|
|
125
|
+
|
|
126
|
+
<template>
|
|
127
|
+
<div class="space-y-8">
|
|
128
|
+
<p class="text-[11px] text-slate-400">
|
|
129
|
+
{{ t('settings.workspaceSettings.budget.body') }}
|
|
130
|
+
</p>
|
|
131
|
+
|
|
132
|
+
<!-- Workspace tier -->
|
|
133
|
+
<section class="space-y-2">
|
|
134
|
+
<h3 class="text-sm font-semibold text-slate-200">
|
|
135
|
+
{{ t('settings.workspaceSettings.budget.workspace') }}
|
|
136
|
+
</h3>
|
|
137
|
+
<div class="grid grid-cols-2 gap-3">
|
|
138
|
+
<label class="block">
|
|
139
|
+
<span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">
|
|
140
|
+
{{ t('settings.workspaceSettings.budget.monthlyLimit') }}
|
|
141
|
+
</span>
|
|
142
|
+
<UInput
|
|
143
|
+
v-model="wsDraft.spendMonthlyLimit"
|
|
144
|
+
type="number"
|
|
145
|
+
:min="0"
|
|
146
|
+
:placeholder="t('settings.workspaceSettings.budget.defaultPlaceholder')"
|
|
147
|
+
size="sm"
|
|
148
|
+
/>
|
|
149
|
+
</label>
|
|
150
|
+
<label class="block">
|
|
151
|
+
<span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">
|
|
152
|
+
{{ t('settings.workspaceSettings.budget.currency') }}
|
|
153
|
+
</span>
|
|
154
|
+
<UInput
|
|
155
|
+
v-model="wsDraft.spendCurrency"
|
|
156
|
+
placeholder="EUR"
|
|
157
|
+
maxlength="3"
|
|
158
|
+
size="sm"
|
|
159
|
+
class="uppercase"
|
|
160
|
+
/>
|
|
161
|
+
</label>
|
|
162
|
+
</div>
|
|
163
|
+
<div v-if="workspace.spend" class="text-[11px] text-slate-400">
|
|
164
|
+
{{
|
|
165
|
+
t('settings.workspaceSettings.budget.spent', {
|
|
166
|
+
spent: money(workspace.spend.costSpent, workspace.spend.currency),
|
|
167
|
+
limit: money(workspace.spend.costLimit, workspace.spend.currency),
|
|
168
|
+
})
|
|
169
|
+
}}
|
|
170
|
+
</div>
|
|
171
|
+
<div class="flex justify-end">
|
|
172
|
+
<UButton
|
|
173
|
+
color="primary"
|
|
174
|
+
icon="i-lucide-save"
|
|
175
|
+
size="sm"
|
|
176
|
+
:loading="savingWorkspace"
|
|
177
|
+
@click="saveWorkspace"
|
|
178
|
+
>
|
|
179
|
+
{{ t('settings.workspaceSettings.budget.saveTier') }}
|
|
180
|
+
</UButton>
|
|
181
|
+
</div>
|
|
182
|
+
</section>
|
|
183
|
+
|
|
184
|
+
<!-- Account tier -->
|
|
185
|
+
<section v-if="account" class="space-y-2">
|
|
186
|
+
<h3 class="text-sm font-semibold text-slate-200">
|
|
187
|
+
{{ t('settings.workspaceSettings.budget.account') }}
|
|
188
|
+
</h3>
|
|
189
|
+
<p class="text-[11px] text-slate-400">
|
|
190
|
+
{{ t('settings.workspaceSettings.budget.accountBody') }}
|
|
191
|
+
</p>
|
|
192
|
+
<label class="block">
|
|
193
|
+
<span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">
|
|
194
|
+
{{ t('settings.workspaceSettings.budget.monthlyLimit') }}
|
|
195
|
+
</span>
|
|
196
|
+
<UInput
|
|
197
|
+
v-model="accountDraft"
|
|
198
|
+
type="number"
|
|
199
|
+
:min="0"
|
|
200
|
+
:max="accountCap ?? undefined"
|
|
201
|
+
:disabled="!canEditAccount"
|
|
202
|
+
:placeholder="t('settings.workspaceSettings.budget.noLimitPlaceholder')"
|
|
203
|
+
size="sm"
|
|
204
|
+
/>
|
|
205
|
+
</label>
|
|
206
|
+
<p
|
|
207
|
+
v-if="accountCap != null"
|
|
208
|
+
class="text-[11px]"
|
|
209
|
+
:class="accountOverCap ? 'text-rose-400' : 'text-amber-400'"
|
|
210
|
+
>
|
|
211
|
+
{{ t('settings.workspaceSettings.budget.hardCap', { amount: money(accountCap) }) }}
|
|
212
|
+
<span class="text-slate-500"
|
|
213
|
+
>({{ t('settings.workspaceSettings.budget.hardCapHint') }})</span
|
|
214
|
+
>
|
|
215
|
+
</p>
|
|
216
|
+
<div v-if="workspace.accountSpend" class="text-[11px] text-slate-400">
|
|
217
|
+
{{
|
|
218
|
+
t('settings.workspaceSettings.budget.spent', {
|
|
219
|
+
spent: money(workspace.accountSpend.costSpent),
|
|
220
|
+
limit: money(workspace.accountSpend.costLimit),
|
|
221
|
+
})
|
|
222
|
+
}}
|
|
223
|
+
</div>
|
|
224
|
+
<p v-if="!canEditAccount" class="text-[11px] text-slate-500">
|
|
225
|
+
{{ t('settings.workspaceSettings.budget.adminOnly') }}
|
|
226
|
+
</p>
|
|
227
|
+
<div v-if="canEditAccount" class="flex justify-end">
|
|
228
|
+
<UButton
|
|
229
|
+
color="primary"
|
|
230
|
+
icon="i-lucide-save"
|
|
231
|
+
size="sm"
|
|
232
|
+
:loading="savingAccount"
|
|
233
|
+
:disabled="accountOverCap"
|
|
234
|
+
@click="saveAccount"
|
|
235
|
+
>
|
|
236
|
+
{{ t('settings.workspaceSettings.budget.saveTier') }}
|
|
237
|
+
</UButton>
|
|
238
|
+
</div>
|
|
239
|
+
</section>
|
|
240
|
+
|
|
241
|
+
<!-- User tier -->
|
|
242
|
+
<section class="space-y-2">
|
|
243
|
+
<h3 class="text-sm font-semibold text-slate-200">
|
|
244
|
+
{{ t('settings.workspaceSettings.budget.user') }}
|
|
245
|
+
</h3>
|
|
246
|
+
<p class="text-[11px] text-slate-400">
|
|
247
|
+
{{ t('settings.workspaceSettings.budget.userBody') }}
|
|
248
|
+
</p>
|
|
249
|
+
<label class="block">
|
|
250
|
+
<span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">
|
|
251
|
+
{{ t('settings.workspaceSettings.budget.monthlyLimit') }}
|
|
252
|
+
</span>
|
|
253
|
+
<UInput
|
|
254
|
+
v-model="userDraft"
|
|
255
|
+
type="number"
|
|
256
|
+
:min="0"
|
|
257
|
+
:max="userCap ?? undefined"
|
|
258
|
+
:placeholder="t('settings.workspaceSettings.budget.noLimitPlaceholder')"
|
|
259
|
+
size="sm"
|
|
260
|
+
/>
|
|
261
|
+
</label>
|
|
262
|
+
<p
|
|
263
|
+
v-if="userCap != null"
|
|
264
|
+
class="text-[11px]"
|
|
265
|
+
:class="userOverCap ? 'text-rose-400' : 'text-amber-400'"
|
|
266
|
+
>
|
|
267
|
+
{{ t('settings.workspaceSettings.budget.hardCap', { amount: money(userCap) }) }}
|
|
268
|
+
<span class="text-slate-500"
|
|
269
|
+
>({{ t('settings.workspaceSettings.budget.hardCapHint') }})</span
|
|
270
|
+
>
|
|
271
|
+
</p>
|
|
272
|
+
<div v-if="workspace.userSpend" class="text-[11px] text-slate-400">
|
|
273
|
+
{{
|
|
274
|
+
t('settings.workspaceSettings.budget.spent', {
|
|
275
|
+
spent: money(workspace.userSpend.costSpent),
|
|
276
|
+
limit: money(workspace.userSpend.costLimit),
|
|
277
|
+
})
|
|
278
|
+
}}
|
|
279
|
+
</div>
|
|
280
|
+
<div class="flex justify-end">
|
|
281
|
+
<UButton
|
|
282
|
+
color="primary"
|
|
283
|
+
icon="i-lucide-save"
|
|
284
|
+
size="sm"
|
|
285
|
+
:loading="savingUser"
|
|
286
|
+
:disabled="userOverCap"
|
|
287
|
+
@click="saveUser"
|
|
288
|
+
>
|
|
289
|
+
{{ t('settings.workspaceSettings.budget.saveTier') }}
|
|
290
|
+
</UButton>
|
|
291
|
+
</div>
|
|
292
|
+
</section>
|
|
293
|
+
</div>
|
|
294
|
+
</template>
|
|
@@ -12,6 +12,7 @@ import type { CreateTaskType, TaskLimitMode } from '~/types/domain'
|
|
|
12
12
|
import MergeThresholdsPanel from '~/components/settings/MergeThresholdsPanel.vue'
|
|
13
13
|
import IssueTrackerPanel from '~/components/settings/IssueTrackerPanel.vue'
|
|
14
14
|
import ServiceFragmentDefaultsPanel from '~/components/settings/ServiceFragmentDefaultsPanel.vue'
|
|
15
|
+
import BudgetSettings from '~/components/settings/BudgetSettings.vue'
|
|
15
16
|
import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
|
|
16
17
|
|
|
17
18
|
const { t, te } = useI18n()
|
|
@@ -114,9 +115,6 @@ const draft = reactive({
|
|
|
114
115
|
storeAgentContext: true,
|
|
115
116
|
artifactRetentionDays: 14,
|
|
116
117
|
kaizenEnabled: true,
|
|
117
|
-
// Budget: empty string ⇒ "use the built-in default" (null on the wire).
|
|
118
|
-
spendCurrency: '',
|
|
119
|
-
spendMonthlyLimit: '',
|
|
120
118
|
})
|
|
121
119
|
|
|
122
120
|
function hydrate() {
|
|
@@ -129,8 +127,6 @@ function hydrate() {
|
|
|
129
127
|
draft.storeAgentContext = s.storeAgentContext
|
|
130
128
|
draft.artifactRetentionDays = s.artifactRetentionDays
|
|
131
129
|
draft.kaizenEnabled = s.kaizenEnabled
|
|
132
|
-
draft.spendCurrency = s.spendCurrency ?? ''
|
|
133
|
-
draft.spendMonthlyLimit = s.spendMonthlyLimit == null ? '' : String(s.spendMonthlyLimit)
|
|
134
130
|
}
|
|
135
131
|
|
|
136
132
|
// `store.settings` is always replaced wholesale (store hydrate/update reassign the ref),
|
|
@@ -176,46 +172,6 @@ async function save() {
|
|
|
176
172
|
saving.value = false
|
|
177
173
|
}
|
|
178
174
|
}
|
|
179
|
-
|
|
180
|
-
const savingBudget = ref(false)
|
|
181
|
-
|
|
182
|
-
async function saveBudget() {
|
|
183
|
-
savingBudget.value = true
|
|
184
|
-
// The number input emits a raw number once edited but starts as a string from hydrate, so
|
|
185
|
-
// coerce through String() before trimming. Blank ⇒ "use the built-in default" (null on the wire).
|
|
186
|
-
const raw = String(draft.spendMonthlyLimit ?? '').trim()
|
|
187
|
-
const monthlyLimit = raw === '' ? null : Number(raw)
|
|
188
|
-
try {
|
|
189
|
-
await store.update({
|
|
190
|
-
spendCurrency: draft.spendCurrency.trim() ? draft.spendCurrency.trim().toUpperCase() : null,
|
|
191
|
-
spendMonthlyLimit: monthlyLimit,
|
|
192
|
-
})
|
|
193
|
-
toast.add({
|
|
194
|
-
title: t('settings.workspaceSettings.toast.budgetSaved'),
|
|
195
|
-
icon: 'i-lucide-check',
|
|
196
|
-
color: 'success',
|
|
197
|
-
})
|
|
198
|
-
// The settings PUT only returns the settings; re-fetch the snapshot so the toolbar's
|
|
199
|
-
// spend meter reflects the newly-set limit/currency (spendService.status) right away.
|
|
200
|
-
// Best-effort and AFTER the save succeeded: a transient snapshot-refresh failure must
|
|
201
|
-
// not report a successfully-saved budget as failed (the meter also catches up on the
|
|
202
|
-
// next snapshot pushed over the stream).
|
|
203
|
-
try {
|
|
204
|
-
await useWorkspaceStore().refresh()
|
|
205
|
-
} catch {
|
|
206
|
-
// ignore — the budget is persisted; the meter will catch up on the next snapshot.
|
|
207
|
-
}
|
|
208
|
-
} catch (e) {
|
|
209
|
-
toast.add({
|
|
210
|
-
title: t('settings.workspaceSettings.toast.budgetSaveFailed'),
|
|
211
|
-
description: e instanceof Error ? e.message : String(e),
|
|
212
|
-
icon: 'i-lucide-triangle-alert',
|
|
213
|
-
color: 'error',
|
|
214
|
-
})
|
|
215
|
-
} finally {
|
|
216
|
-
savingBudget.value = false
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
175
|
</script>
|
|
220
176
|
|
|
221
177
|
<template>
|
|
@@ -370,56 +326,9 @@ async function saveBudget() {
|
|
|
370
326
|
</div>
|
|
371
327
|
</template>
|
|
372
328
|
|
|
373
|
-
<!-- Budget -->
|
|
329
|
+
<!-- Budget (workspace / account / user tiers) -->
|
|
374
330
|
<template #budget>
|
|
375
|
-
<
|
|
376
|
-
<section class="space-y-2">
|
|
377
|
-
<h3 class="text-sm font-semibold text-slate-200">
|
|
378
|
-
{{ t('settings.workspaceSettings.budget.heading') }}
|
|
379
|
-
</h3>
|
|
380
|
-
<p class="text-[11px] text-slate-400">
|
|
381
|
-
{{ t('settings.workspaceSettings.budget.body') }}
|
|
382
|
-
</p>
|
|
383
|
-
<div class="grid grid-cols-2 gap-3">
|
|
384
|
-
<label class="block">
|
|
385
|
-
<span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">
|
|
386
|
-
{{ t('settings.workspaceSettings.budget.monthlyLimit') }}
|
|
387
|
-
</span>
|
|
388
|
-
<UInput
|
|
389
|
-
v-model="draft.spendMonthlyLimit"
|
|
390
|
-
type="number"
|
|
391
|
-
:min="0"
|
|
392
|
-
:placeholder="t('settings.workspaceSettings.budget.defaultPlaceholder')"
|
|
393
|
-
size="sm"
|
|
394
|
-
/>
|
|
395
|
-
</label>
|
|
396
|
-
<label class="block">
|
|
397
|
-
<span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">
|
|
398
|
-
{{ t('settings.workspaceSettings.budget.currency') }}
|
|
399
|
-
</span>
|
|
400
|
-
<UInput
|
|
401
|
-
v-model="draft.spendCurrency"
|
|
402
|
-
placeholder="EUR"
|
|
403
|
-
maxlength="3"
|
|
404
|
-
size="sm"
|
|
405
|
-
class="uppercase"
|
|
406
|
-
/>
|
|
407
|
-
</label>
|
|
408
|
-
</div>
|
|
409
|
-
</section>
|
|
410
|
-
|
|
411
|
-
<div class="flex justify-end">
|
|
412
|
-
<UButton
|
|
413
|
-
color="primary"
|
|
414
|
-
icon="i-lucide-save"
|
|
415
|
-
size="sm"
|
|
416
|
-
:loading="savingBudget"
|
|
417
|
-
@click="saveBudget"
|
|
418
|
-
>
|
|
419
|
-
{{ t('settings.workspaceSettings.budget.save') }}
|
|
420
|
-
</UButton>
|
|
421
|
-
</div>
|
|
422
|
-
</div>
|
|
331
|
+
<BudgetSettings />
|
|
423
332
|
</template>
|
|
424
333
|
|
|
425
334
|
<!-- Merge thresholds -->
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { getUserSettingsContract, updateUserSettingsContract } from '@cat-factory/contracts'
|
|
2
|
+
import type { UpdateUserSettingsInput } from '~/types/domain'
|
|
3
|
+
import type { ApiContext } from './context'
|
|
4
|
+
|
|
5
|
+
/** Per-user settings (the user-tier spend budget), scoped to the signed-in user. */
|
|
6
|
+
export function userSettingsApi({ send }: ApiContext) {
|
|
7
|
+
return {
|
|
8
|
+
getUserSettings: () => send(getUserSettingsContract, {}),
|
|
9
|
+
updateUserSettings: (body: UpdateUserSettingsInput) =>
|
|
10
|
+
send(updateUserSettingsContract, { body }),
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -36,6 +36,7 @@ import { slackApi } from './api/slack'
|
|
|
36
36
|
import { specApi } from './api/spec'
|
|
37
37
|
import { tasksApi } from './api/tasks'
|
|
38
38
|
import { userSecretsApi } from './api/userSecrets'
|
|
39
|
+
import { userSettingsApi } from './api/userSettings'
|
|
39
40
|
import { workspacesApi } from './api/workspaces'
|
|
40
41
|
|
|
41
42
|
/**
|
|
@@ -131,5 +132,6 @@ export function useApi() {
|
|
|
131
132
|
...slackApi(ctx),
|
|
132
133
|
...bootstrapApi(ctx),
|
|
133
134
|
...userSecretsApi(ctx),
|
|
135
|
+
...userSettingsApi(ctx),
|
|
134
136
|
}
|
|
135
137
|
}
|
package/app/stores/accounts.ts
CHANGED
|
@@ -67,6 +67,17 @@ export const useAccountsStore = defineStore(
|
|
|
67
67
|
return updated
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Set an account's monthly spend budget (the account tier). Admin-only on the
|
|
72
|
+
* backend; `null` clears the limit. Patches the loaded account in place on success.
|
|
73
|
+
*/
|
|
74
|
+
async function setSpendMonthlyLimit(id: string, limit: number | null) {
|
|
75
|
+
const updated = await api.updateAccount(id, { spendMonthlyLimit: limit })
|
|
76
|
+
const i = accounts.value.findIndex((a) => a.id === id)
|
|
77
|
+
if (i >= 0) accounts.value[i] = updated
|
|
78
|
+
return updated
|
|
79
|
+
}
|
|
80
|
+
|
|
70
81
|
// ---- members + invitations -------------------------------------------
|
|
71
82
|
|
|
72
83
|
const members = ref<AccountMember[]>([])
|
|
@@ -139,6 +150,7 @@ export const useAccountsStore = defineStore(
|
|
|
139
150
|
createOrg,
|
|
140
151
|
switchTo,
|
|
141
152
|
setDefaultCloudProvider,
|
|
153
|
+
setSpendMonthlyLimit,
|
|
142
154
|
loadRoster,
|
|
143
155
|
invite,
|
|
144
156
|
revokeInvite,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import type { UpdateUserSettingsInput, UserSettings } from '~/types/domain'
|
|
4
|
+
|
|
5
|
+
const DEFAULTS: UserSettings = {
|
|
6
|
+
spendMonthlyLimit: null,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The signed-in user's personal settings (today: the user-tier spend budget). Hydrated
|
|
11
|
+
* from the workspace snapshot's `userSettings`; `update` persists via `PUT /user-settings`
|
|
12
|
+
* and patches the local copy. Empty (defaults) when no user is signed in.
|
|
13
|
+
*/
|
|
14
|
+
export const useUserSettingsStore = defineStore('userSettings', () => {
|
|
15
|
+
const api = useApi()
|
|
16
|
+
const settings = ref<UserSettings>({ ...DEFAULTS })
|
|
17
|
+
|
|
18
|
+
function hydrate(value: UserSettings | null) {
|
|
19
|
+
settings.value = value ? { ...DEFAULTS, ...value } : { ...DEFAULTS }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function update(patch: UpdateUserSettingsInput) {
|
|
23
|
+
settings.value = await api.updateUserSettings(patch)
|
|
24
|
+
return settings.value
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return { settings, hydrate, update }
|
|
28
|
+
})
|
package/app/stores/workspace.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { defineStore } from 'pinia'
|
|
2
2
|
import { computed, ref } from 'vue'
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
BudgetCaps,
|
|
5
|
+
InfraSetup,
|
|
6
|
+
SpendStatus,
|
|
7
|
+
Workspace,
|
|
8
|
+
WorkspaceSnapshot,
|
|
9
|
+
} from '~/types/domain'
|
|
4
10
|
import { useAccountsStore } from '~/stores/accounts'
|
|
5
11
|
import { useBoardStore } from '~/stores/board'
|
|
6
12
|
import { usePipelinesStore } from '~/stores/pipelines'
|
|
@@ -50,8 +56,14 @@ export const useWorkspaceStore = defineStore(
|
|
|
50
56
|
const ready = ref(false)
|
|
51
57
|
/** Set when bootstrap fails so the UI can show a retry. */
|
|
52
58
|
const error = ref<string | null>(null)
|
|
53
|
-
/** Latest spend-safeguard status from the server (null until first load). */
|
|
59
|
+
/** Latest WORKSPACE-tier spend-safeguard status from the server (null until first load). */
|
|
54
60
|
const spend = ref<SpendStatus | null>(null)
|
|
61
|
+
/** ACCOUNT-tier spend status (null when the tier is inactive or unavailable). */
|
|
62
|
+
const accountSpend = ref<SpendStatus | null>(null)
|
|
63
|
+
/** USER-tier spend status for the signed-in caller (null when inactive). */
|
|
64
|
+
const userSpend = ref<SpendStatus | null>(null)
|
|
65
|
+
/** Operator hard ceilings on the account/user budget tiers (null until first load). */
|
|
66
|
+
const budgetCaps = ref<BudgetCaps | null>(null)
|
|
55
67
|
/**
|
|
56
68
|
* Per-area infrastructure-setup status (ephemeral environments / agent executor / binary
|
|
57
69
|
* storage) from the snapshot, driving the infra-setup banner. Null on an older backend that
|
|
@@ -93,6 +105,10 @@ export const useWorkspaceStore = defineStore(
|
|
|
93
105
|
}
|
|
94
106
|
workspaceId.value = snapshot.workspace.id
|
|
95
107
|
spend.value = snapshot.spend ?? null
|
|
108
|
+
accountSpend.value = snapshot.accountSpend ?? null
|
|
109
|
+
userSpend.value = snapshot.userSpend ?? null
|
|
110
|
+
budgetCaps.value = snapshot.budgetCaps ?? null
|
|
111
|
+
useUserSettingsStore().hydrate(snapshot.userSettings ?? null)
|
|
96
112
|
infraSetup.value = snapshot.infraSetup ?? null
|
|
97
113
|
// Keep the board list in step (e.g. a freshly created board, or a rename).
|
|
98
114
|
const i = workspaces.value.findIndex((w) => w.id === snapshot.workspace.id)
|
|
@@ -254,6 +270,9 @@ export const useWorkspaceStore = defineStore(
|
|
|
254
270
|
ready,
|
|
255
271
|
error,
|
|
256
272
|
spend,
|
|
273
|
+
accountSpend,
|
|
274
|
+
userSpend,
|
|
275
|
+
budgetCaps,
|
|
257
276
|
infraSetup,
|
|
258
277
|
init,
|
|
259
278
|
switchTo,
|
package/app/types/domain.ts
CHANGED
|
@@ -57,11 +57,14 @@ export type {
|
|
|
57
57
|
CustomAgentKind,
|
|
58
58
|
Pipeline,
|
|
59
59
|
SpendStatus,
|
|
60
|
+
BudgetCaps,
|
|
60
61
|
Workspace,
|
|
61
62
|
WorkspaceSnapshot,
|
|
62
63
|
TaskLimitMode,
|
|
63
64
|
WorkspaceSettings,
|
|
64
65
|
UpdateWorkspaceSettingsInput,
|
|
66
|
+
UserSettings,
|
|
67
|
+
UpdateUserSettingsInput,
|
|
65
68
|
InfraSetup,
|
|
66
69
|
InfraSetupStatus,
|
|
67
70
|
InfraSetupArea,
|
package/i18n/locales/en.json
CHANGED
|
@@ -2367,11 +2367,22 @@
|
|
|
2367
2367
|
},
|
|
2368
2368
|
"budget": {
|
|
2369
2369
|
"heading": "Monthly spend budget",
|
|
2370
|
-
"body": "Token usage is metered per LLM call, priced, and gated by
|
|
2370
|
+
"body": "Token usage is metered per LLM call, priced, and gated by these budgets. A run pauses when any tier it belongs to is exhausted. Leave a field blank for no limit on that tier (the workspace tier then inherits the built-in default, about 100 EUR/month).",
|
|
2371
2371
|
"monthlyLimit": "Monthly limit",
|
|
2372
2372
|
"defaultPlaceholder": "Default",
|
|
2373
2373
|
"currency": "Currency (ISO 4217)",
|
|
2374
|
-
"save": "Save budget"
|
|
2374
|
+
"save": "Save budget",
|
|
2375
|
+
"saveTier": "Save",
|
|
2376
|
+
"workspace": "This workspace",
|
|
2377
|
+
"account": "Account (all workspaces)",
|
|
2378
|
+
"user": "You (all your runs)",
|
|
2379
|
+
"accountBody": "A ceiling across every workspace in this account.",
|
|
2380
|
+
"userBody": "A ceiling across every run you start, in any workspace.",
|
|
2381
|
+
"hardCap": "Operator limit: {amount}",
|
|
2382
|
+
"hardCapHint": "set by the deployment; the value can't exceed it",
|
|
2383
|
+
"spent": "{spent} of {limit} spent this month",
|
|
2384
|
+
"adminOnly": "Only an account admin can change the account budget.",
|
|
2385
|
+
"noLimitPlaceholder": "No limit"
|
|
2375
2386
|
},
|
|
2376
2387
|
"toast": {
|
|
2377
2388
|
"saved": "Settings saved",
|
package/i18n/locales/es.json
CHANGED
|
@@ -2193,11 +2193,22 @@
|
|
|
2193
2193
|
},
|
|
2194
2194
|
"budget": {
|
|
2195
2195
|
"heading": "Presupuesto de gasto mensual",
|
|
2196
|
-
"body": "El uso de tokens se contabiliza por llamada al LLM, se valora y se limita con
|
|
2196
|
+
"body": "El uso de tokens se contabiliza por llamada al LLM, se valora y se limita con estos presupuestos. Una ejecución se pausa cuando se agota cualquier nivel al que pertenece. Deja un campo en blanco para no poner límite a ese nivel (el nivel del espacio de trabajo hereda entonces el valor predeterminado integrado, unos 100 EUR/mes).",
|
|
2197
2197
|
"monthlyLimit": "Límite mensual",
|
|
2198
2198
|
"defaultPlaceholder": "Predeterminado",
|
|
2199
2199
|
"currency": "Moneda (ISO 4217)",
|
|
2200
|
-
"save": "Guardar presupuesto"
|
|
2200
|
+
"save": "Guardar presupuesto",
|
|
2201
|
+
"saveTier": "Guardar",
|
|
2202
|
+
"workspace": "Este espacio de trabajo",
|
|
2203
|
+
"account": "Cuenta (todos los espacios de trabajo)",
|
|
2204
|
+
"user": "Tú (todas tus ejecuciones)",
|
|
2205
|
+
"accountBody": "Un tope para todos los espacios de trabajo de esta cuenta.",
|
|
2206
|
+
"userBody": "Un tope para todas las ejecuciones que inicies, en cualquier espacio de trabajo.",
|
|
2207
|
+
"hardCap": "Límite del operador: {amount}",
|
|
2208
|
+
"hardCapHint": "definido por el despliegue; el valor no puede superarlo",
|
|
2209
|
+
"spent": "{spent} de {limit} gastado este mes",
|
|
2210
|
+
"adminOnly": "Solo un administrador de la cuenta puede cambiar el presupuesto de la cuenta.",
|
|
2211
|
+
"noLimitPlaceholder": "Sin límite"
|
|
2201
2212
|
},
|
|
2202
2213
|
"toast": {
|
|
2203
2214
|
"saved": "Configuración guardada",
|
package/i18n/locales/fr.json
CHANGED
|
@@ -2193,11 +2193,22 @@
|
|
|
2193
2193
|
},
|
|
2194
2194
|
"budget": {
|
|
2195
2195
|
"heading": "Budget de dépenses mensuel",
|
|
2196
|
-
"body": "L'utilisation des jetons est décomptée par appel LLM, valorisée et plafonnée par
|
|
2196
|
+
"body": "L'utilisation des jetons est décomptée par appel LLM, valorisée et plafonnée par ces budgets. Une exécution est mise en pause dès qu'un niveau auquel elle appartient est épuisé. Laissez un champ vide pour ne fixer aucune limite à ce niveau (le niveau de l'espace de travail hérite alors de la valeur par défaut intégrée, environ 100 EUR/mois).",
|
|
2197
2197
|
"monthlyLimit": "Limite mensuelle",
|
|
2198
2198
|
"defaultPlaceholder": "Par défaut",
|
|
2199
2199
|
"currency": "Devise (ISO 4217)",
|
|
2200
|
-
"save": "Enregistrer le budget"
|
|
2200
|
+
"save": "Enregistrer le budget",
|
|
2201
|
+
"saveTier": "Enregistrer",
|
|
2202
|
+
"workspace": "Cet espace de travail",
|
|
2203
|
+
"account": "Compte (tous les espaces de travail)",
|
|
2204
|
+
"user": "Vous (toutes vos exécutions)",
|
|
2205
|
+
"accountBody": "Un plafond pour tous les espaces de travail de ce compte.",
|
|
2206
|
+
"userBody": "Un plafond pour toutes les exécutions que vous lancez, dans n'importe quel espace de travail.",
|
|
2207
|
+
"hardCap": "Limite de l'opérateur : {amount}",
|
|
2208
|
+
"hardCapHint": "définie par le déploiement ; la valeur ne peut pas la dépasser",
|
|
2209
|
+
"spent": "{spent} sur {limit} dépensé ce mois-ci",
|
|
2210
|
+
"adminOnly": "Seul un administrateur du compte peut modifier le budget du compte.",
|
|
2211
|
+
"noLimitPlaceholder": "Aucune limite"
|
|
2201
2212
|
},
|
|
2202
2213
|
"toast": {
|
|
2203
2214
|
"saved": "Paramètres enregistrés",
|
package/i18n/locales/he.json
CHANGED
|
@@ -2314,11 +2314,22 @@
|
|
|
2314
2314
|
},
|
|
2315
2315
|
"budget": {
|
|
2316
2316
|
"heading": "תקציב הוצאה חודשי",
|
|
2317
|
-
"body": "שימוש באסימונים נמדד לכל קריאת LLM, מתומחר ומוגבל על ידי
|
|
2317
|
+
"body": "שימוש באסימונים נמדד לכל קריאת LLM, מתומחר ומוגבל על ידי תקציבים אלה. הרצה מושהית כאשר כל שכבה שאליה היא שייכת מוצתה. השאר שדה ריק כדי לא להגביל את אותה שכבה (שכבת סביבת העבודה יורשת אז את ברירת המחדל המובנית, כ-100 EUR לחודש).",
|
|
2318
2318
|
"monthlyLimit": "מגבלה חודשית",
|
|
2319
2319
|
"defaultPlaceholder": "ברירת מחדל",
|
|
2320
2320
|
"currency": "מטבע (ISO 4217)",
|
|
2321
|
-
"save": "שמור תקציב"
|
|
2321
|
+
"save": "שמור תקציב",
|
|
2322
|
+
"saveTier": "שמור",
|
|
2323
|
+
"workspace": "סביבת עבודה זו",
|
|
2324
|
+
"account": "חשבון (כל סביבות העבודה)",
|
|
2325
|
+
"user": "אתה (כל ההרצות שלך)",
|
|
2326
|
+
"accountBody": "תקרה לכל סביבות העבודה בחשבון זה.",
|
|
2327
|
+
"userBody": "תקרה לכל הרצה שאתה מתחיל, בכל סביבת עבודה.",
|
|
2328
|
+
"hardCap": "מגבלת מפעיל: {amount}",
|
|
2329
|
+
"hardCapHint": "נקבעת על ידי הפריסה; הערך אינו יכול לחרוג ממנה",
|
|
2330
|
+
"spent": "{spent} מתוך {limit} הוצאו החודש",
|
|
2331
|
+
"adminOnly": "רק מנהל חשבון יכול לשנות את תקציב החשבון.",
|
|
2332
|
+
"noLimitPlaceholder": "ללא הגבלה"
|
|
2322
2333
|
},
|
|
2323
2334
|
"toast": {
|
|
2324
2335
|
"saved": "ההגדרות נשמרו",
|
package/i18n/locales/ja.json
CHANGED
|
@@ -2315,11 +2315,22 @@
|
|
|
2315
2315
|
},
|
|
2316
2316
|
"budget": {
|
|
2317
2317
|
"heading": "月間の支出予算",
|
|
2318
|
-
"body": "トークン使用量は LLM
|
|
2318
|
+
"body": "トークン使用量は LLM 呼び出しごとに計測され、価格付けされ、これらの予算で制限されます。実行は、それが属するいずれかの階層が使い切られると一時停止します。その階層に上限を設けない場合はフィールドを空欄のままにします (ワークスペース階層は組み込みのデフォルト、月額約 100 EUR を継承します)。",
|
|
2319
2319
|
"monthlyLimit": "月間上限",
|
|
2320
2320
|
"defaultPlaceholder": "デフォルト",
|
|
2321
2321
|
"currency": "通貨 (ISO 4217)",
|
|
2322
|
-
"save": "予算を保存"
|
|
2322
|
+
"save": "予算を保存",
|
|
2323
|
+
"saveTier": "保存",
|
|
2324
|
+
"workspace": "このワークスペース",
|
|
2325
|
+
"account": "アカウント (すべてのワークスペース)",
|
|
2326
|
+
"user": "あなた (あなたのすべての実行)",
|
|
2327
|
+
"accountBody": "このアカウント内のすべてのワークスペースにまたがる上限です。",
|
|
2328
|
+
"userBody": "任意のワークスペースであなたが開始するすべての実行にまたがる上限です。",
|
|
2329
|
+
"hardCap": "運用上限: {amount}",
|
|
2330
|
+
"hardCapHint": "デプロイによって設定されます。この値を超えることはできません",
|
|
2331
|
+
"spent": "今月は {limit} のうち {spent} を使用しました",
|
|
2332
|
+
"adminOnly": "アカウント予算を変更できるのはアカウント管理者のみです。",
|
|
2333
|
+
"noLimitPlaceholder": "上限なし"
|
|
2323
2334
|
},
|
|
2324
2335
|
"toast": {
|
|
2325
2336
|
"saved": "設定を保存しました",
|
package/i18n/locales/pl.json
CHANGED
|
@@ -2193,11 +2193,22 @@
|
|
|
2193
2193
|
},
|
|
2194
2194
|
"budget": {
|
|
2195
2195
|
"heading": "Miesięczny budżet wydatków",
|
|
2196
|
-
"body": "Zużycie tokenów jest mierzone na wywołanie LLM, wyceniane i ograniczane
|
|
2196
|
+
"body": "Zużycie tokenów jest mierzone na wywołanie LLM, wyceniane i ograniczane tymi budżetami. Uruchomienie jest wstrzymywane, gdy dowolny poziom, do którego należy, zostanie wyczerpany. Pozostaw pole puste, aby nie ustawiać limitu dla tego poziomu (poziom obszaru roboczego dziedziczy wtedy wbudowaną wartość domyślną, około 100 EUR/miesiąc).",
|
|
2197
2197
|
"monthlyLimit": "Limit miesięczny",
|
|
2198
2198
|
"defaultPlaceholder": "Domyślny",
|
|
2199
2199
|
"currency": "Waluta (ISO 4217)",
|
|
2200
|
-
"save": "Zapisz budżet"
|
|
2200
|
+
"save": "Zapisz budżet",
|
|
2201
|
+
"saveTier": "Zapisz",
|
|
2202
|
+
"workspace": "Ten obszar roboczy",
|
|
2203
|
+
"account": "Konto (wszystkie obszary robocze)",
|
|
2204
|
+
"user": "Ty (wszystkie Twoje uruchomienia)",
|
|
2205
|
+
"accountBody": "Pułap dla wszystkich obszarów roboczych na tym koncie.",
|
|
2206
|
+
"userBody": "Pułap dla każdego uruchomienia, które rozpoczniesz, w dowolnym obszarze roboczym.",
|
|
2207
|
+
"hardCap": "Limit operatora: {amount}",
|
|
2208
|
+
"hardCapHint": "ustawiony przez wdrożenie; wartość nie może go przekroczyć",
|
|
2209
|
+
"spent": "Wydano {spent} z {limit} w tym miesiącu",
|
|
2210
|
+
"adminOnly": "Tylko administrator konta może zmienić budżet konta.",
|
|
2211
|
+
"noLimitPlaceholder": "Bez limitu"
|
|
2201
2212
|
},
|
|
2202
2213
|
"toast": {
|
|
2203
2214
|
"saved": "Ustawienia zapisane",
|
package/i18n/locales/tr.json
CHANGED
|
@@ -2315,11 +2315,22 @@
|
|
|
2315
2315
|
},
|
|
2316
2316
|
"budget": {
|
|
2317
2317
|
"heading": "Aylık harcama bütçesi",
|
|
2318
|
-
"body": "Token kullanımı her LLM çağrısı için ölçülür, fiyatlandırılır ve bu
|
|
2318
|
+
"body": "Token kullanımı her LLM çağrısı için ölçülür, fiyatlandırılır ve bu bütçelerle sınırlandırılır. Bir çalıştırma, ait olduğu herhangi bir katman tükendiğinde duraklatılır. O katmanda sınır olmaması için bir alanı boş bırakın (çalışma alanı katmanı bu durumda yerleşik varsayılanı, aylık yaklaşık 100 EUR devralır).",
|
|
2319
2319
|
"monthlyLimit": "Aylık sınır",
|
|
2320
2320
|
"defaultPlaceholder": "Varsayılan",
|
|
2321
2321
|
"currency": "Para birimi (ISO 4217)",
|
|
2322
|
-
"save": "Bütçeyi kaydet"
|
|
2322
|
+
"save": "Bütçeyi kaydet",
|
|
2323
|
+
"saveTier": "Kaydet",
|
|
2324
|
+
"workspace": "Bu çalışma alanı",
|
|
2325
|
+
"account": "Hesap (tüm çalışma alanları)",
|
|
2326
|
+
"user": "Siz (başlattığınız tüm çalıştırmalar)",
|
|
2327
|
+
"accountBody": "Bu hesaptaki tüm çalışma alanlarını kapsayan bir üst sınır.",
|
|
2328
|
+
"userBody": "Herhangi bir çalışma alanında başlattığınız her çalıştırmayı kapsayan bir üst sınır.",
|
|
2329
|
+
"hardCap": "Operatör sınırı: {amount}",
|
|
2330
|
+
"hardCapHint": "dağıtım tarafından belirlenir; değer bunu aşamaz",
|
|
2331
|
+
"spent": "Bu ay {limit} bütçenin {spent} kadarı harcandı",
|
|
2332
|
+
"adminOnly": "Yalnızca bir hesap yöneticisi hesap bütçesini değiştirebilir.",
|
|
2333
|
+
"noLimitPlaceholder": "Sınır yok"
|
|
2323
2334
|
},
|
|
2324
2335
|
"toast": {
|
|
2325
2336
|
"saved": "Ayarlar kaydedildi",
|
package/i18n/locales/uk.json
CHANGED
|
@@ -2193,11 +2193,22 @@
|
|
|
2193
2193
|
},
|
|
2194
2194
|
"budget": {
|
|
2195
2195
|
"heading": "Місячний бюджет витрат",
|
|
2196
|
-
"body": "Використання токенів вимірюється за кожен виклик LLM, оцінюється та обмежується
|
|
2196
|
+
"body": "Використання токенів вимірюється за кожен виклик LLM, оцінюється та обмежується цими бюджетами. Запуск призупиняється, коли вичерпано будь-який рівень, до якого він належить. Залиште поле порожнім, щоб не встановлювати ліміт для цього рівня (рівень робочого простору тоді успадковує вбудоване значення за замовчуванням, близько 100 EUR/місяць).",
|
|
2197
2197
|
"monthlyLimit": "Місячний ліміт",
|
|
2198
2198
|
"defaultPlaceholder": "За замовчуванням",
|
|
2199
2199
|
"currency": "Валюта (ISO 4217)",
|
|
2200
|
-
"save": "Зберегти бюджет"
|
|
2200
|
+
"save": "Зберегти бюджет",
|
|
2201
|
+
"saveTier": "Зберегти",
|
|
2202
|
+
"workspace": "Цей робочий простір",
|
|
2203
|
+
"account": "Обліковий запис (усі робочі простори)",
|
|
2204
|
+
"user": "Ви (усі ваші запуски)",
|
|
2205
|
+
"accountBody": "Стеля для всіх робочих просторів у цьому обліковому записі.",
|
|
2206
|
+
"userBody": "Стеля для кожного запуску, який ви розпочинаєте, у будь-якому робочому просторі.",
|
|
2207
|
+
"hardCap": "Ліміт оператора: {amount}",
|
|
2208
|
+
"hardCapHint": "встановлюється розгортанням; значення не може його перевищити",
|
|
2209
|
+
"spent": "Цього місяця витрачено {spent} з {limit}",
|
|
2210
|
+
"adminOnly": "Лише адміністратор облікового запису може змінити бюджет облікового запису.",
|
|
2211
|
+
"noLimitPlaceholder": "Без обмеження"
|
|
2201
2212
|
},
|
|
2202
2213
|
"toast": {
|
|
2203
2214
|
"saved": "Налаштування збережено",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.99.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.109.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@toad-contracts/testing": "0.3.2",
|