@cat-factory/app 0.46.11 → 0.47.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/auth/AuthGate.vue +3 -2
- package/app/components/auth/LoginScreen.vue +172 -32
- package/app/components/auth/ResetPasswordScreen.vue +16 -13
- package/app/components/auth/UserMenu.vue +3 -2
- package/app/components/layout/AccountDeploymentSettings.vue +307 -37
- package/app/components/layout/AccountFragmentSettings.vue +2 -3
- package/app/components/layout/AccountTeamSettings.vue +75 -45
- package/app/components/layout/AiProvidersBanner.vue +11 -11
- package/app/components/layout/BoardSwitcher.vue +63 -29
- package/app/components/layout/CommandBar.vue +68 -54
- package/app/components/layout/GitHubPatBanner.vue +13 -6
- package/app/components/layout/IntegrationBackTitle.vue +4 -1
- package/app/components/layout/IntegrationsHub.vue +67 -49
- package/app/components/layout/NotificationsInbox.vue +48 -19
- package/app/components/layout/PersonalSetupModal.vue +26 -16
- package/app/components/layout/ProviderConfigBanner.vue +14 -9
- package/app/components/layout/SideBar.vue +20 -18
- package/app/components/layout/SpendWarningBanner.vue +16 -16
- package/app/components/providers/ApiKeysSection.vue +27 -3
- package/app/components/providers/PersonalSubscriptionSection.vue +18 -4
- package/app/components/providers/SignInRequiredNotice.vue +19 -0
- package/app/components/settings/AccountSettingsPanel.vue +9 -6
- package/app/components/settings/IssueTrackerPanel.vue +114 -53
- package/app/components/settings/LocalModeSettingsPanel.vue +60 -28
- package/app/components/settings/LocalModelEndpointsPanel.vue +72 -27
- package/app/components/settings/MergeThresholdsPanel.vue +93 -45
- package/app/components/settings/ModelConfigurationPanel.vue +62 -36
- package/app/components/settings/ObservabilityConnectionPanel.vue +65 -35
- package/app/components/settings/OpenRouterCatalogPanel.vue +70 -40
- package/app/components/settings/ProviderConnectionPanel.vue +115 -61
- package/app/components/settings/ServiceFragmentDefaultsPanel.vue +9 -12
- package/app/components/settings/UserSecretsSection.vue +37 -18
- package/app/components/settings/WorkspaceSettingsPanel.vue +114 -62
- package/app/composables/api/auth.ts +7 -0
- package/app/stores/auth.ts +25 -1
- package/app/types/accountSettings.ts +4 -0
- package/i18n/locales/en.json +908 -0
- package/i18n/locales/es.json +899 -0
- package/i18n/locales/fr.json +899 -0
- package/i18n/locales/pl.json +899 -0
- package/i18n/locales/uk.json +899 -0
- package/package.json +2 -2
|
@@ -11,6 +11,7 @@ import { computed, ref, watch } from 'vue'
|
|
|
11
11
|
import type { OpenRouterModelMeta } from '~/types/openrouter'
|
|
12
12
|
import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
|
|
13
13
|
|
|
14
|
+
const { t } = useI18n()
|
|
14
15
|
const ui = useUiStore()
|
|
15
16
|
const workspace = useWorkspaceStore()
|
|
16
17
|
const store = useOpenRouterStore()
|
|
@@ -89,11 +90,16 @@ const recommendedAvailable = computed(() => {
|
|
|
89
90
|
|
|
90
91
|
function contextLabel(tokens: number | undefined): string {
|
|
91
92
|
if (!tokens) return ''
|
|
92
|
-
return tokens >= 1000
|
|
93
|
+
return tokens >= 1000
|
|
94
|
+
? t('settings.openRouterCatalog.contextThousands', { value: Math.round(tokens / 1000) })
|
|
95
|
+
: t('settings.openRouterCatalog.context', { value: tokens })
|
|
93
96
|
}
|
|
94
97
|
|
|
95
98
|
function priceLabel(m: OpenRouterModelMeta): string {
|
|
96
|
-
return
|
|
99
|
+
return t('settings.openRouterCatalog.price', {
|
|
100
|
+
input: m.inputPerMillion,
|
|
101
|
+
output: m.outputPerMillion,
|
|
102
|
+
})
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
function toggle(id: string, on: boolean) {
|
|
@@ -132,8 +138,8 @@ async function connectKey() {
|
|
|
132
138
|
else await apiKeys.removeUserKey(created.id).catch(() => {})
|
|
133
139
|
}
|
|
134
140
|
toast.add({
|
|
135
|
-
title: '
|
|
136
|
-
description: store.refreshError ?? '
|
|
141
|
+
title: t('settings.openRouterCatalog.toast.connectFailed'),
|
|
142
|
+
description: store.refreshError ?? t('settings.openRouterCatalog.toast.rejected'),
|
|
137
143
|
icon: 'i-lucide-triangle-alert',
|
|
138
144
|
color: 'error',
|
|
139
145
|
})
|
|
@@ -141,10 +147,14 @@ async function connectKey() {
|
|
|
141
147
|
}
|
|
142
148
|
keyValue.value = ''
|
|
143
149
|
keyLabel.value = ''
|
|
144
|
-
toast.add({
|
|
150
|
+
toast.add({
|
|
151
|
+
title: t('settings.openRouterCatalog.toast.connected'),
|
|
152
|
+
icon: 'i-lucide-check',
|
|
153
|
+
color: 'success',
|
|
154
|
+
})
|
|
145
155
|
} catch (e) {
|
|
146
156
|
toast.add({
|
|
147
|
-
title: '
|
|
157
|
+
title: t('settings.openRouterCatalog.toast.connectFailed'),
|
|
148
158
|
description: e instanceof Error ? e.message : String(e),
|
|
149
159
|
icon: 'i-lucide-triangle-alert',
|
|
150
160
|
color: 'error',
|
|
@@ -159,8 +169,8 @@ async function refresh() {
|
|
|
159
169
|
const result = await store.refresh(workspace.workspaceId)
|
|
160
170
|
if (!result.reachable) {
|
|
161
171
|
toast.add({
|
|
162
|
-
title: '
|
|
163
|
-
description: store.refreshError ?? '
|
|
172
|
+
title: t('settings.openRouterCatalog.toast.unreachable'),
|
|
173
|
+
description: store.refreshError ?? t('settings.openRouterCatalog.toast.connectFirst'),
|
|
164
174
|
icon: 'i-lucide-triangle-alert',
|
|
165
175
|
color: 'error',
|
|
166
176
|
})
|
|
@@ -179,10 +189,14 @@ async function save() {
|
|
|
179
189
|
await store.save(workspace.workspaceId, models2)
|
|
180
190
|
// Reflect newly-enabled models in the picker immediately.
|
|
181
191
|
await models.refresh(workspace.workspaceId)
|
|
182
|
-
toast.add({
|
|
192
|
+
toast.add({
|
|
193
|
+
title: t('settings.openRouterCatalog.toast.saved'),
|
|
194
|
+
icon: 'i-lucide-check',
|
|
195
|
+
color: 'success',
|
|
196
|
+
})
|
|
183
197
|
} catch (e) {
|
|
184
198
|
toast.add({
|
|
185
|
-
title: '
|
|
199
|
+
title: t('settings.openRouterCatalog.toast.saveFailed'),
|
|
186
200
|
description: e instanceof Error ? e.message : String(e),
|
|
187
201
|
icon: 'i-lucide-triangle-alert',
|
|
188
202
|
color: 'error',
|
|
@@ -206,9 +220,11 @@ function manageKeys() {
|
|
|
206
220
|
<template #body>
|
|
207
221
|
<div class="space-y-4">
|
|
208
222
|
<p class="text-xs text-slate-400">
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
223
|
+
<i18n-t keypath="settings.openRouterCatalog.intro" tag="span" scope="global">
|
|
224
|
+
<template #models>
|
|
225
|
+
<strong>{{ t('settings.openRouterCatalog.introModels') }}</strong>
|
|
226
|
+
</template>
|
|
227
|
+
</i18n-t>
|
|
212
228
|
</p>
|
|
213
229
|
|
|
214
230
|
<!-- Step 1: connect a key (inline) — hidden once a key is connected -->
|
|
@@ -217,40 +233,49 @@ function manageKeys() {
|
|
|
217
233
|
class="space-y-3 rounded-lg border border-slate-700 bg-slate-900/60 p-4"
|
|
218
234
|
>
|
|
219
235
|
<h4 class="text-xs font-semibold uppercase tracking-wide text-slate-500">
|
|
220
|
-
|
|
236
|
+
{{ t('settings.openRouterCatalog.connectHeading') }}
|
|
221
237
|
</h4>
|
|
222
238
|
<ol class="list-decimal space-y-1 pl-5 text-sm text-slate-300">
|
|
223
239
|
<li>
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
240
|
+
<i18n-t keypath="settings.openRouterCatalog.step1" tag="span" scope="global">
|
|
241
|
+
<template #link>
|
|
242
|
+
<a
|
|
243
|
+
href="https://openrouter.ai/keys"
|
|
244
|
+
target="_blank"
|
|
245
|
+
rel="noopener noreferrer"
|
|
246
|
+
class="text-primary-400 underline"
|
|
247
|
+
>{{ t('settings.openRouterCatalog.step1Link') }}</a
|
|
248
|
+
>
|
|
249
|
+
</template>
|
|
250
|
+
</i18n-t>
|
|
233
251
|
</li>
|
|
234
252
|
<li>
|
|
235
|
-
|
|
253
|
+
<i18n-t keypath="settings.openRouterCatalog.step2" tag="span" scope="global">
|
|
254
|
+
<template #prefix>
|
|
255
|
+
<span class="font-mono">sk-or-…</span>
|
|
256
|
+
</template>
|
|
257
|
+
</i18n-t>
|
|
236
258
|
</li>
|
|
237
259
|
</ol>
|
|
238
260
|
<div class="flex flex-wrap items-end gap-3">
|
|
239
|
-
<UFormField label="
|
|
261
|
+
<UFormField :label="t('settings.openRouterCatalog.scope')">
|
|
240
262
|
<USelect
|
|
241
263
|
v-model="keyScope"
|
|
242
264
|
:items="[
|
|
243
|
-
{ label: '
|
|
244
|
-
{ label: '
|
|
265
|
+
{ label: t('settings.openRouterCatalog.scopeWorkspace'), value: 'workspace' },
|
|
266
|
+
{ label: t('settings.openRouterCatalog.scopeUser'), value: 'user' },
|
|
245
267
|
]"
|
|
246
268
|
class="w-48"
|
|
247
269
|
/>
|
|
248
270
|
</UFormField>
|
|
249
|
-
<UFormField label="
|
|
250
|
-
<UInput
|
|
271
|
+
<UFormField :label="t('settings.openRouterCatalog.labelOptional')" class="flex-1">
|
|
272
|
+
<UInput
|
|
273
|
+
v-model="keyLabel"
|
|
274
|
+
:placeholder="t('settings.openRouterCatalog.labelPlaceholder')"
|
|
275
|
+
/>
|
|
251
276
|
</UFormField>
|
|
252
277
|
</div>
|
|
253
|
-
<UFormField label="
|
|
278
|
+
<UFormField :label="t('settings.openRouterCatalog.apiKey')">
|
|
254
279
|
<UTextarea
|
|
255
280
|
v-model="keyValue"
|
|
256
281
|
:rows="2"
|
|
@@ -265,7 +290,7 @@ function manageKeys() {
|
|
|
265
290
|
icon="i-lucide-plus"
|
|
266
291
|
@click="connectKey()"
|
|
267
292
|
>
|
|
268
|
-
|
|
293
|
+
{{ t('settings.openRouterCatalog.connectAndBrowse') }}
|
|
269
294
|
</UButton>
|
|
270
295
|
</div>
|
|
271
296
|
</div>
|
|
@@ -277,10 +302,10 @@ function manageKeys() {
|
|
|
277
302
|
>
|
|
278
303
|
<span class="flex items-center gap-2 text-slate-300">
|
|
279
304
|
<UIcon name="i-lucide-check-circle" class="h-4 w-4 text-emerald-400" />
|
|
280
|
-
|
|
305
|
+
{{ t('settings.openRouterCatalog.keyConnected') }}
|
|
281
306
|
</span>
|
|
282
307
|
<UButton color="neutral" variant="ghost" size="xs" @click="manageKeys()">
|
|
283
|
-
|
|
308
|
+
{{ t('settings.openRouterCatalog.manageKeys') }}
|
|
284
309
|
</UButton>
|
|
285
310
|
</div>
|
|
286
311
|
|
|
@@ -295,7 +320,7 @@ function manageKeys() {
|
|
|
295
320
|
:loading="store.refreshing"
|
|
296
321
|
@click="refresh()"
|
|
297
322
|
>
|
|
298
|
-
|
|
323
|
+
{{ t('settings.openRouterCatalog.refresh') }}
|
|
299
324
|
</UButton>
|
|
300
325
|
<UButton
|
|
301
326
|
v-if="recommendedAvailable.length"
|
|
@@ -305,14 +330,14 @@ function manageKeys() {
|
|
|
305
330
|
icon="i-lucide-sparkles"
|
|
306
331
|
@click="enableRecommended()"
|
|
307
332
|
>
|
|
308
|
-
|
|
333
|
+
{{ t('settings.openRouterCatalog.enableRecommended') }}
|
|
309
334
|
</UButton>
|
|
310
335
|
<UInput
|
|
311
336
|
v-model="filter"
|
|
312
337
|
size="sm"
|
|
313
338
|
class="flex-1"
|
|
314
339
|
icon="i-lucide-search"
|
|
315
|
-
placeholder="
|
|
340
|
+
:placeholder="t('settings.openRouterCatalog.filterPlaceholder')"
|
|
316
341
|
/>
|
|
317
342
|
</div>
|
|
318
343
|
|
|
@@ -341,12 +366,17 @@ function manageKeys() {
|
|
|
341
366
|
</label>
|
|
342
367
|
</div>
|
|
343
368
|
<p v-else class="text-xs text-slate-500">
|
|
344
|
-
|
|
345
|
-
|
|
369
|
+
<i18n-t keypath="settings.openRouterCatalog.empty" tag="span" scope="global">
|
|
370
|
+
<template #action>
|
|
371
|
+
<span class="text-slate-300">{{ t('settings.openRouterCatalog.refresh') }}</span>
|
|
372
|
+
</template>
|
|
373
|
+
</i18n-t>
|
|
346
374
|
</p>
|
|
347
375
|
|
|
348
376
|
<div class="flex items-center justify-between">
|
|
349
|
-
<span class="text-xs text-slate-500">{{
|
|
377
|
+
<span class="text-xs text-slate-500">{{
|
|
378
|
+
t('settings.openRouterCatalog.enabledCount', { count: selectedCount }, selectedCount)
|
|
379
|
+
}}</span>
|
|
350
380
|
<UButton
|
|
351
381
|
color="primary"
|
|
352
382
|
variant="soft"
|
|
@@ -355,7 +385,7 @@ function manageKeys() {
|
|
|
355
385
|
:loading="busy"
|
|
356
386
|
@click="save()"
|
|
357
387
|
>
|
|
358
|
-
|
|
388
|
+
{{ t('common.save') }}
|
|
359
389
|
</UButton>
|
|
360
390
|
</div>
|
|
361
391
|
</template>
|
|
@@ -12,23 +12,28 @@ import type { ProviderConnectionKind } from '~/types/providerConnections'
|
|
|
12
12
|
import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
|
|
13
13
|
import ProvisioningLogsDrawer from '~/components/provisioning/ProvisioningLogsDrawer.vue'
|
|
14
14
|
|
|
15
|
+
const { t } = useI18n()
|
|
15
16
|
const ui = useUiStore()
|
|
16
17
|
const store = useProviderConnectionsStore()
|
|
17
18
|
const toast = useToast()
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
20
|
+
// Per-kind icon (display-only). The title + blurb copy resolve through the i18n catalog
|
|
21
|
+
// via literal keys keyed off the provider-connection kind (see TITLE_KEYS / BLURB_KEYS).
|
|
22
|
+
const ICONS: Record<ProviderConnectionKind, string> = {
|
|
23
|
+
environment: 'i-lucide-cloud',
|
|
24
|
+
'runner-pool': 'i-lucide-server-cog',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Exhaustive Record keyed off the provider-connection-kind union (a missing member fails
|
|
28
|
+
// the typecheck); each value is a LITERAL catalog key so the typed-message-keys check sees
|
|
29
|
+
// it. Leaf keys mirror the kind verbatim.
|
|
30
|
+
const TITLE_KEYS: Record<ProviderConnectionKind, string> = {
|
|
31
|
+
environment: 'settings.providerConnection.kind.environment.title',
|
|
32
|
+
'runner-pool': 'settings.providerConnection.kind.runner-pool.title',
|
|
33
|
+
}
|
|
34
|
+
const BLURB_KEYS: Record<ProviderConnectionKind, string> = {
|
|
35
|
+
environment: 'settings.providerConnection.kind.environment.blurb',
|
|
36
|
+
'runner-pool': 'settings.providerConnection.kind.runner-pool.blurb',
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
const kind = computed<ProviderConnectionKind | null>(() => ui.providerConnectionKind)
|
|
@@ -40,7 +45,15 @@ const open = computed({
|
|
|
40
45
|
})
|
|
41
46
|
const back = useIntegrationBack(open)
|
|
42
47
|
|
|
43
|
-
const meta = computed(() =>
|
|
48
|
+
const meta = computed(() =>
|
|
49
|
+
kind.value
|
|
50
|
+
? {
|
|
51
|
+
title: t(TITLE_KEYS[kind.value]),
|
|
52
|
+
icon: ICONS[kind.value],
|
|
53
|
+
blurb: t(BLURB_KEYS[kind.value]),
|
|
54
|
+
}
|
|
55
|
+
: null,
|
|
56
|
+
)
|
|
44
57
|
const descriptor = computed(() => (kind.value ? store.descriptorFor(kind.value) : null))
|
|
45
58
|
const connection = computed(() => (kind.value ? store.connectionFor(kind.value) : null))
|
|
46
59
|
|
|
@@ -69,7 +82,7 @@ async function setDelegation(patch: {
|
|
|
69
82
|
try {
|
|
70
83
|
await settings.update(patch)
|
|
71
84
|
} catch (e) {
|
|
72
|
-
notifyError('
|
|
85
|
+
notifyError(t('settings.providerConnection.delegation.updateFailed'), e)
|
|
73
86
|
} finally {
|
|
74
87
|
savingDelegation.value = false
|
|
75
88
|
}
|
|
@@ -226,9 +239,13 @@ async function save() {
|
|
|
226
239
|
await store.updateSecrets(kind.value, buildSecretsOnly())
|
|
227
240
|
}
|
|
228
241
|
resetDraft()
|
|
229
|
-
toast.add({
|
|
242
|
+
toast.add({
|
|
243
|
+
title: t('settings.providerConnection.toast.saved', { title: meta.value?.title ?? '' }),
|
|
244
|
+
icon: 'i-lucide-check',
|
|
245
|
+
color: 'success',
|
|
246
|
+
})
|
|
230
247
|
} catch (e) {
|
|
231
|
-
notifyError('
|
|
248
|
+
notifyError(t('settings.providerConnection.toast.saveFailed'), e)
|
|
232
249
|
} finally {
|
|
233
250
|
busy.value = false
|
|
234
251
|
}
|
|
@@ -240,9 +257,9 @@ async function remove() {
|
|
|
240
257
|
try {
|
|
241
258
|
await store.remove(kind.value)
|
|
242
259
|
resetDraft()
|
|
243
|
-
toast.add({ title: '
|
|
260
|
+
toast.add({ title: t('settings.providerConnection.toast.removed'), icon: 'i-lucide-check' })
|
|
244
261
|
} catch (e) {
|
|
245
|
-
notifyError('
|
|
262
|
+
notifyError(t('settings.providerConnection.toast.removeFailed'), e)
|
|
246
263
|
} finally {
|
|
247
264
|
busy.value = false
|
|
248
265
|
}
|
|
@@ -254,16 +271,24 @@ function fieldHelp(key: string): string | undefined {
|
|
|
254
271
|
if (!f) return undefined
|
|
255
272
|
const filled = (values.value[key] ?? '').trim()
|
|
256
273
|
if (f.default !== undefined && !filled) {
|
|
257
|
-
|
|
274
|
+
const defaulted = t('settings.providerConnection.field.defaultsTo', { value: f.default })
|
|
275
|
+
return f.help ? `${f.help} · ${defaulted}` : defaulted
|
|
258
276
|
}
|
|
259
277
|
return f.help
|
|
260
278
|
}
|
|
261
279
|
</script>
|
|
262
280
|
|
|
263
281
|
<template>
|
|
264
|
-
<UModal
|
|
282
|
+
<UModal
|
|
283
|
+
v-model:open="open"
|
|
284
|
+
:title="meta?.title ?? t('settings.providerConnection.fallbackTitle')"
|
|
285
|
+
:ui="{ content: 'max-w-xl' }"
|
|
286
|
+
>
|
|
265
287
|
<template #title>
|
|
266
|
-
<IntegrationBackTitle
|
|
288
|
+
<IntegrationBackTitle
|
|
289
|
+
:title="meta?.title ?? t('settings.providerConnection.fallbackTitle')"
|
|
290
|
+
@back="back"
|
|
291
|
+
/>
|
|
267
292
|
</template>
|
|
268
293
|
<template #body>
|
|
269
294
|
<!-- Local-mode infrastructure delegation: the local-vs-external choice for BOTH
|
|
@@ -273,11 +298,11 @@ function fieldHelp(key: string): string | undefined {
|
|
|
273
298
|
class="mb-4 space-y-3 rounded-lg border border-slate-700 bg-slate-900/40 p-3"
|
|
274
299
|
>
|
|
275
300
|
<div>
|
|
276
|
-
<h3 class="text-sm font-semibold text-slate-200">
|
|
301
|
+
<h3 class="text-sm font-semibold text-slate-200">
|
|
302
|
+
{{ t('settings.providerConnection.delegation.title') }}
|
|
303
|
+
</h3>
|
|
277
304
|
<p class="mt-1 text-[11px] text-slate-400">
|
|
278
|
-
|
|
279
|
-
Tester's infrastructure via in-container docker-compose. Opt in below to delegate either
|
|
280
|
-
concern to an external service instead. Applies only in local mode.
|
|
305
|
+
{{ t('settings.providerConnection.delegation.intro') }}
|
|
281
306
|
</p>
|
|
282
307
|
</div>
|
|
283
308
|
|
|
@@ -290,20 +315,28 @@ function fieldHelp(key: string): string | undefined {
|
|
|
290
315
|
:disabled="savingDelegation || !runnerPoolRegistered"
|
|
291
316
|
@update:model-value="(v) => setDelegation({ delegateAgentsToRunnerPool: v })"
|
|
292
317
|
/>
|
|
293
|
-
<span class="text-sm text-slate-200">
|
|
318
|
+
<span class="text-sm text-slate-200">
|
|
319
|
+
{{ t('settings.providerConnection.delegation.agentsToggle') }}
|
|
320
|
+
</span>
|
|
294
321
|
</label>
|
|
295
322
|
<p class="pl-9 text-[11px] text-slate-400">
|
|
296
|
-
|
|
297
|
-
self-hosted runner pool instead of host Docker.
|
|
323
|
+
{{ t('settings.providerConnection.delegation.agentsHint') }}
|
|
298
324
|
<template v-if="!runnerPoolRegistered">
|
|
299
|
-
<
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
325
|
+
<i18n-t
|
|
326
|
+
keypath="settings.providerConnection.delegation.registerPoolPrompt"
|
|
327
|
+
tag="span"
|
|
328
|
+
scope="global"
|
|
303
329
|
>
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
330
|
+
<template #link>
|
|
331
|
+
<button
|
|
332
|
+
type="button"
|
|
333
|
+
class="text-sky-400 underline underline-offset-2 hover:text-sky-300"
|
|
334
|
+
@click="openRunnerPoolPanel"
|
|
335
|
+
>
|
|
336
|
+
{{ t('settings.providerConnection.delegation.registerPoolLink') }}
|
|
337
|
+
</button>
|
|
338
|
+
</template>
|
|
339
|
+
</i18n-t>
|
|
307
340
|
</template>
|
|
308
341
|
</p>
|
|
309
342
|
</div>
|
|
@@ -318,12 +351,11 @@ function fieldHelp(key: string): string | undefined {
|
|
|
318
351
|
@update:model-value="(v) => setDelegation({ delegateTestEnvToProvider: v })"
|
|
319
352
|
/>
|
|
320
353
|
<span class="text-sm text-slate-200">
|
|
321
|
-
|
|
354
|
+
{{ t('settings.providerConnection.delegation.envToggle') }}
|
|
322
355
|
</span>
|
|
323
356
|
</label>
|
|
324
357
|
<p class="pl-9 text-[11px] text-slate-400">
|
|
325
|
-
|
|
326
|
-
below instead of in-container docker-compose. Connect a provider first to enable this.
|
|
358
|
+
{{ t('settings.providerConnection.delegation.envHint') }}
|
|
327
359
|
</p>
|
|
328
360
|
</div>
|
|
329
361
|
</section>
|
|
@@ -334,15 +366,17 @@ function fieldHelp(key: string): string | undefined {
|
|
|
334
366
|
v-if="isLocal && kind === 'runner-pool'"
|
|
335
367
|
class="mb-4 rounded-md border border-slate-700 bg-slate-900/40 px-3 py-2 text-[11px] text-slate-400"
|
|
336
368
|
>
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
369
|
+
<i18n-t keypath="settings.providerConnection.runnerPoolLocalHint" tag="span" scope="global">
|
|
370
|
+
<template #link>
|
|
371
|
+
<button
|
|
372
|
+
type="button"
|
|
373
|
+
class="text-sky-400 underline underline-offset-2 hover:text-sky-300"
|
|
374
|
+
@click="ui.openProviderConnection('environment')"
|
|
375
|
+
>
|
|
376
|
+
{{ t('settings.providerConnection.ephemeralEnvironments') }}
|
|
377
|
+
</button>
|
|
378
|
+
</template>
|
|
379
|
+
</i18n-t>
|
|
346
380
|
</p>
|
|
347
381
|
|
|
348
382
|
<div v-if="descriptor" class="space-y-4">
|
|
@@ -355,7 +389,11 @@ function fieldHelp(key: string): string | undefined {
|
|
|
355
389
|
class="shrink-0"
|
|
356
390
|
@click="showLogs = !showLogs"
|
|
357
391
|
>
|
|
358
|
-
{{
|
|
392
|
+
{{
|
|
393
|
+
showLogs
|
|
394
|
+
? t('settings.providerConnection.hideLogs')
|
|
395
|
+
: t('settings.providerConnection.viewLogs')
|
|
396
|
+
}}
|
|
359
397
|
</UButton>
|
|
360
398
|
</div>
|
|
361
399
|
|
|
@@ -369,7 +407,9 @@ function fieldHelp(key: string): string | undefined {
|
|
|
369
407
|
>
|
|
370
408
|
<div>
|
|
371
409
|
<span class="font-medium text-slate-200">{{ connection.label }}</span>
|
|
372
|
-
<div class="text-[11px] text-emerald-400">
|
|
410
|
+
<div class="text-[11px] text-emerald-400">
|
|
411
|
+
{{ t('settings.providerConnection.connectedAt', { baseUrl: connection.baseUrl }) }}
|
|
412
|
+
</div>
|
|
373
413
|
</div>
|
|
374
414
|
<UButton
|
|
375
415
|
icon="i-lucide-trash-2"
|
|
@@ -386,7 +426,11 @@ function fieldHelp(key: string): string | undefined {
|
|
|
386
426
|
v-if="descriptor.missingRequired.length"
|
|
387
427
|
class="rounded-md border border-amber-500/40 bg-amber-950/40 px-3 py-2 text-xs text-amber-200"
|
|
388
428
|
>
|
|
389
|
-
|
|
429
|
+
{{
|
|
430
|
+
t('settings.providerConnection.missingConfig', {
|
|
431
|
+
fields: descriptor.missingRequired.join(', '),
|
|
432
|
+
})
|
|
433
|
+
}}
|
|
390
434
|
</div>
|
|
391
435
|
|
|
392
436
|
<!-- Generic, descriptor-driven field form -->
|
|
@@ -395,7 +439,11 @@ function fieldHelp(key: string): string | undefined {
|
|
|
395
439
|
class="rounded-lg border border-dashed border-slate-700 p-3 space-y-3"
|
|
396
440
|
>
|
|
397
441
|
<p class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
398
|
-
{{
|
|
442
|
+
{{
|
|
443
|
+
connection
|
|
444
|
+
? t('settings.providerConnection.form.updateConfiguration')
|
|
445
|
+
: t('settings.providerConnection.form.connect')
|
|
446
|
+
}}
|
|
399
447
|
</p>
|
|
400
448
|
<!-- A native re-register replaces the whole manifest; secrets are write-only so they
|
|
401
449
|
must be re-supplied. Non-secret config is prefilled, so it survives a save. -->
|
|
@@ -403,15 +451,22 @@ function fieldHelp(key: string): string | undefined {
|
|
|
403
451
|
v-if="connection && canAuthor && hasSecretFields"
|
|
404
452
|
class="text-[11px] text-amber-300/80"
|
|
405
453
|
>
|
|
406
|
-
|
|
407
|
-
|
|
454
|
+
{{
|
|
455
|
+
t(
|
|
456
|
+
'settings.providerConnection.form.reenterSecrets',
|
|
457
|
+
{ count: secretFieldCount },
|
|
458
|
+
secretFieldCount,
|
|
459
|
+
)
|
|
460
|
+
}}
|
|
408
461
|
</p>
|
|
409
462
|
|
|
410
463
|
<UFormField
|
|
411
464
|
v-for="field in descriptor.configFields"
|
|
412
465
|
:key="field.key"
|
|
413
466
|
:label="
|
|
414
|
-
field.
|
|
467
|
+
field.required && field.default === undefined
|
|
468
|
+
? field.label
|
|
469
|
+
: t('settings.providerConnection.form.optionalLabel', { label: field.label })
|
|
415
470
|
"
|
|
416
471
|
:help="fieldHelp(field.key)"
|
|
417
472
|
>
|
|
@@ -439,19 +494,19 @@ function fieldHelp(key: string): string | undefined {
|
|
|
439
494
|
:loading="testing"
|
|
440
495
|
@click="test()"
|
|
441
496
|
>
|
|
442
|
-
|
|
497
|
+
{{ t('settings.providerConnection.test.button') }}
|
|
443
498
|
</UButton>
|
|
444
499
|
<span v-if="testResult && testResult.ok" class="text-xs text-emerald-400">
|
|
445
|
-
{{ testResult.message ?? '
|
|
500
|
+
{{ testResult.message ?? t('settings.providerConnection.test.ok') }}
|
|
446
501
|
</span>
|
|
447
502
|
<span v-else-if="testResult" class="text-xs text-rose-400">
|
|
448
|
-
{{ testResult.message ?? '
|
|
503
|
+
{{ testResult.message ?? t('settings.providerConnection.test.failed') }}
|
|
449
504
|
</span>
|
|
450
505
|
</div>
|
|
451
506
|
|
|
452
507
|
<div class="flex justify-end">
|
|
453
508
|
<UButton color="primary" size="sm" :loading="busy" :disabled="!canSave" @click="save()">
|
|
454
|
-
{{ connection ? '
|
|
509
|
+
{{ connection ? t('common.save') : t('settings.providerConnection.form.connect') }}
|
|
455
510
|
</UButton>
|
|
456
511
|
</div>
|
|
457
512
|
</div>
|
|
@@ -461,8 +516,7 @@ function fieldHelp(key: string): string | undefined {
|
|
|
461
516
|
v-else
|
|
462
517
|
class="rounded-md border border-slate-700 bg-slate-900/40 px-3 py-3 text-xs text-slate-400"
|
|
463
518
|
>
|
|
464
|
-
|
|
465
|
-
available yet — register it via the API for now.
|
|
519
|
+
{{ t('settings.providerConnection.manifestEditorUnavailable') }}
|
|
466
520
|
</div>
|
|
467
521
|
</div>
|
|
468
522
|
</template>
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// serviceFragmentDefaults store (the backend replaces the whole list on each change).
|
|
7
7
|
import { onMounted, ref } from 'vue'
|
|
8
8
|
|
|
9
|
+
const { t } = useI18n()
|
|
9
10
|
const fragments = useFragmentsStore()
|
|
10
11
|
const defaults = useServiceFragmentDefaultsStore()
|
|
11
12
|
const ui = useUiStore()
|
|
@@ -41,7 +42,7 @@ async function save(ids: string[]) {
|
|
|
41
42
|
await defaults.set(ids)
|
|
42
43
|
} catch (e) {
|
|
43
44
|
toast.add({
|
|
44
|
-
title: '
|
|
45
|
+
title: t('settings.serviceFragmentDefaults.saveFailed'),
|
|
45
46
|
description: e instanceof Error ? e.message : String(e),
|
|
46
47
|
icon: 'i-lucide-triangle-alert',
|
|
47
48
|
color: 'error',
|
|
@@ -64,16 +65,12 @@ function remove(id: string) {
|
|
|
64
65
|
<template>
|
|
65
66
|
<div class="space-y-4">
|
|
66
67
|
<p class="text-xs text-slate-400">
|
|
67
|
-
|
|
68
|
-
with. Their guidance is folded into the prompt of every
|
|
69
|
-
<span class="text-slate-300">code-aware</span> agent (coder, reviewer, architect, fixers) on
|
|
70
|
-
the service's tasks. You can refine the set per service in its inspector; changing this
|
|
71
|
-
default does not affect services that already exist.
|
|
68
|
+
{{ t('settings.serviceFragmentDefaults.intro') }}
|
|
72
69
|
</p>
|
|
73
70
|
|
|
74
71
|
<div class="flex items-center justify-between">
|
|
75
72
|
<span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
76
|
-
|
|
73
|
+
{{ t('settings.serviceFragmentDefaults.defaultFragments') }}
|
|
77
74
|
</span>
|
|
78
75
|
<UDropdownMenu v-if="menu.length" :items="menu" :ui="{ content: 'max-h-72 overflow-y-auto' }">
|
|
79
76
|
<UButton
|
|
@@ -84,7 +81,7 @@ function remove(id: string) {
|
|
|
84
81
|
trailing-icon="i-lucide-chevron-down"
|
|
85
82
|
:loading="busy"
|
|
86
83
|
>
|
|
87
|
-
|
|
84
|
+
{{ t('settings.serviceFragmentDefaults.addFragment') }}
|
|
88
85
|
</UButton>
|
|
89
86
|
</UDropdownMenu>
|
|
90
87
|
</div>
|
|
@@ -104,26 +101,26 @@ function remove(id: string) {
|
|
|
104
101
|
</UBadge>
|
|
105
102
|
</div>
|
|
106
103
|
<p v-else class="text-[11px] text-slate-500">
|
|
107
|
-
|
|
104
|
+
{{ t('settings.serviceFragmentDefaults.empty') }}
|
|
108
105
|
</p>
|
|
109
106
|
|
|
110
107
|
<div class="flex flex-wrap gap-x-4 gap-y-1 border-t border-slate-800 pt-3 text-[11px]">
|
|
111
108
|
<span class="text-slate-500">
|
|
112
|
-
|
|
109
|
+
{{ t('settings.serviceFragmentDefaults.footer.question') }}
|
|
113
110
|
</span>
|
|
114
111
|
<button
|
|
115
112
|
type="button"
|
|
116
113
|
class="font-medium text-primary-400 hover:underline"
|
|
117
114
|
@click="ui.openFragmentLibrary()"
|
|
118
115
|
>
|
|
119
|
-
|
|
116
|
+
{{ t('settings.serviceFragmentDefaults.footer.manageBoard') }}
|
|
120
117
|
</button>
|
|
121
118
|
<button
|
|
122
119
|
type="button"
|
|
123
120
|
class="font-medium text-primary-400 hover:underline"
|
|
124
121
|
@click="ui.openAccountSettings('fragments')"
|
|
125
122
|
>
|
|
126
|
-
|
|
123
|
+
{{ t('settings.serviceFragmentDefaults.footer.manageAccount') }}
|
|
127
124
|
</button>
|
|
128
125
|
</div>
|
|
129
126
|
</div>
|