@cat-factory/app 0.36.0 → 0.37.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/UserMenu.vue +11 -1
- package/app/components/brainstorm/BrainstormWindow.vue +2 -1
- package/app/components/clarity/ClarityReviewWindow.vue +2 -1
- package/app/components/layout/IntegrationBackTitle.vue +12 -7
- package/app/components/layout/IntegrationsHub.vue +191 -43
- package/app/components/layout/PersonalSetupModal.vue +141 -0
- package/app/components/pipeline/PipelineBuilder.vue +1 -1
- package/app/components/providers/VendorCredentialsModal.vue +7 -2
- package/app/composables/api/accounts.ts +36 -51
- package/app/composables/api/auth.ts +20 -19
- package/app/composables/api/board.ts +60 -40
- package/app/composables/api/bootstrap.ts +25 -22
- package/app/composables/api/client.ts +102 -0
- package/app/composables/api/context.ts +25 -6
- package/app/composables/api/documents.ts +36 -34
- package/app/composables/api/execution.ts +65 -48
- package/app/composables/api/followUps.ts +26 -26
- package/app/composables/api/fragments.ts +47 -34
- package/app/composables/api/github.ts +65 -45
- package/app/composables/api/humanReview.ts +7 -6
- package/app/composables/api/humanTest.ts +15 -11
- package/app/composables/api/kaizen.ts +8 -6
- package/app/composables/api/localSettings.ts +5 -4
- package/app/composables/api/models.ts +58 -51
- package/app/composables/api/notifications.ts +13 -7
- package/app/composables/api/presets.ts +34 -28
- package/app/composables/api/providerConnections.ts +68 -26
- package/app/composables/api/recurring.ts +40 -30
- package/app/composables/api/releaseHealth.ts +28 -26
- package/app/composables/api/reviews.ts +136 -114
- package/app/composables/api/sandbox.ts +52 -34
- package/app/composables/api/slack.ts +22 -25
- package/app/composables/api/spec.ts +3 -3
- package/app/composables/api/tasks.ts +42 -41
- package/app/composables/api/userSecrets.ts +12 -17
- package/app/composables/api/workspaces.ts +21 -15
- package/app/composables/useApi.ts +9 -1
- package/app/composables/useIntegrationBack.ts +9 -3
- package/app/pages/index.vue +2 -0
- package/app/stores/auth.ts +2 -1
- package/app/stores/board.ts +2 -1
- package/app/stores/brainstorm.ts +2 -2
- package/app/stores/clarity.ts +6 -2
- package/app/stores/execution.ts +3 -2
- package/app/stores/github.ts +1 -2
- package/app/stores/mergePresets.ts +2 -6
- package/app/stores/pipelines.ts +1 -1
- package/app/stores/recurringPipelines.ts +2 -7
- package/app/stores/sandbox.ts +1 -2
- package/app/stores/ui.ts +62 -19
- package/app/types/accountSettings.ts +11 -36
- package/app/types/accounts.ts +16 -71
- package/app/types/bootstrap.ts +13 -75
- package/app/types/brainstorm.ts +13 -38
- package/app/types/clarity.ts +12 -43
- package/app/types/consensus.ts +16 -89
- package/app/types/documents.ts +19 -94
- package/app/types/domain.ts +54 -586
- package/app/types/execution.ts +48 -515
- package/app/types/fragments.ts +15 -83
- package/app/types/github.ts +25 -161
- package/app/types/incidentEnrichment.ts +10 -25
- package/app/types/localModels.ts +11 -61
- package/app/types/localSettings.ts +9 -26
- package/app/types/merge.ts +10 -68
- package/app/types/model-presets.ts +7 -28
- package/app/types/models.ts +16 -164
- package/app/types/notifications.ts +18 -77
- package/app/types/openrouter.ts +8 -34
- package/app/types/providerConnections.ts +21 -41
- package/app/types/provisioningLogs.ts +9 -29
- package/app/types/recurring.ts +10 -63
- package/app/types/releaseHealth.ts +15 -39
- package/app/types/requirements.ts +14 -84
- package/app/types/sandbox.ts +45 -161
- package/app/types/services.ts +3 -22
- package/app/types/slack.ts +10 -47
- package/app/types/spec.ts +15 -68
- package/app/types/tasks.ts +15 -111
- package/app/types/tracker.ts +9 -24
- package/app/types/userSecrets.ts +12 -47
- package/package.json +9 -2
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { DropdownMenuItem } from '@nuxt/ui'
|
|
3
3
|
|
|
4
|
-
// Signed-in identity +
|
|
4
|
+
// Signed-in identity + per-user actions, shown in the sidebar when auth is enabled.
|
|
5
5
|
const auth = useAuthStore()
|
|
6
|
+
const ui = useUiStore()
|
|
6
7
|
|
|
7
8
|
const items = computed<DropdownMenuItem[][]>(() => [
|
|
9
|
+
[
|
|
10
|
+
{
|
|
11
|
+
// The user-scoped "My setup" hub (personal GitHub token, local runners, personal
|
|
12
|
+
// subscriptions) — kept out of the workspace Integrations hub, reachable here.
|
|
13
|
+
label: 'My setup',
|
|
14
|
+
icon: 'i-lucide-user-cog',
|
|
15
|
+
onSelect: () => ui.openPersonalSetup(),
|
|
16
|
+
},
|
|
17
|
+
],
|
|
8
18
|
[
|
|
9
19
|
{
|
|
10
20
|
label: 'Sign out',
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { parseOutputOutline } from '~/utils/agentOutput'
|
|
11
11
|
import type {
|
|
12
12
|
BrainstormItem,
|
|
13
|
+
BrainstormItemStatus,
|
|
13
14
|
BrainstormSession,
|
|
14
15
|
BrainstormStage,
|
|
15
16
|
ReviewItemCategory,
|
|
@@ -136,7 +137,7 @@ async function submitReply(item: BrainstormItem) {
|
|
|
136
137
|
}
|
|
137
138
|
}
|
|
138
139
|
|
|
139
|
-
async function setStatus(item: BrainstormItem, itemStatus:
|
|
140
|
+
async function setStatus(item: BrainstormItem, itemStatus: BrainstormItemStatus) {
|
|
140
141
|
if (!session.value) return
|
|
141
142
|
try {
|
|
142
143
|
await brainstorm.setItemStatus(session.value, item.id, itemStatus)
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// consumes.
|
|
11
11
|
import { parseOutputOutline } from '~/utils/agentOutput'
|
|
12
12
|
import type {
|
|
13
|
+
ClarityItemStatus,
|
|
13
14
|
ClarityReview,
|
|
14
15
|
ClarityReviewItem,
|
|
15
16
|
ReviewItemCategory,
|
|
@@ -131,7 +132,7 @@ async function submitReply(item: ClarityReviewItem) {
|
|
|
131
132
|
}
|
|
132
133
|
}
|
|
133
134
|
|
|
134
|
-
async function setStatus(item: ClarityReviewItem, itemStatus:
|
|
135
|
+
async function setStatus(item: ClarityReviewItem, itemStatus: ClarityItemStatus) {
|
|
135
136
|
if (!review.value) return
|
|
136
137
|
try {
|
|
137
138
|
await clarity.setItemStatus(review.value, item.id, itemStatus)
|
|
@@ -1,25 +1,30 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
// Title content for an integration sub-panel's modal header. Renders the panel
|
|
3
|
-
// title with a leading "back" control that returns to the
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
3
|
+
// title with a leading "back" control that returns to the hub the panel was reached
|
|
4
|
+
// from — the workspace Integrations hub (`ui.cameFromIntegrations`) or the user-scoped
|
|
5
|
+
// "My setup" hub (`ui.cameFromPersonal`) — shown only when there is one. Panels opened
|
|
6
|
+
// from the command bar, sidebar, a banner or an inspector link don't grow a dead Back.
|
|
7
|
+
// Dropped into a UModal's #title slot, so it inherits the modal's title styling; it
|
|
8
|
+
// emits `back` and the host panel closes itself + reopens the right hub.
|
|
8
9
|
defineProps<{ title?: string }>()
|
|
9
10
|
const emit = defineEmits<{ back: [] }>()
|
|
10
11
|
const ui = useUiStore()
|
|
12
|
+
const cameFromHub = computed(() => ui.cameFromIntegrations || ui.cameFromPersonal)
|
|
13
|
+
const backLabel = computed(() =>
|
|
14
|
+
ui.cameFromPersonal ? 'Back to My setup' : 'Back to Integrations',
|
|
15
|
+
)
|
|
11
16
|
</script>
|
|
12
17
|
|
|
13
18
|
<template>
|
|
14
19
|
<span class="flex items-center gap-1.5">
|
|
15
20
|
<UButton
|
|
16
|
-
v-if="
|
|
21
|
+
v-if="cameFromHub"
|
|
17
22
|
icon="i-lucide-arrow-left"
|
|
18
23
|
color="neutral"
|
|
19
24
|
variant="ghost"
|
|
20
25
|
size="xs"
|
|
21
26
|
class="-ml-1.5 shrink-0"
|
|
22
|
-
aria-label="
|
|
27
|
+
:aria-label="backLabel"
|
|
23
28
|
@click.stop="emit('back')"
|
|
24
29
|
/>
|
|
25
30
|
<span class="min-w-0 truncate">{{ title }}</span>
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
// The Integrations hub: a single modal that lists every external system the
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// opening one closes the hub and reveals that integration's own panel/modal.
|
|
2
|
+
// The Integrations hub: a single modal that lists every external system the WORKSPACE can
|
|
3
|
+
// enable or link in. Each row reuses the existing per-integration panel handlers on the `ui`
|
|
4
|
+
// store (so the integrations themselves are unchanged); opening one closes the hub and
|
|
5
|
+
// reveals that integration's own panel/modal.
|
|
7
6
|
//
|
|
8
|
-
// Sections gate on the same `available` probes the navbar used, so a system that
|
|
9
|
-
//
|
|
7
|
+
// Sections gate on the same `available` probes the navbar used, so a system that the backend
|
|
8
|
+
// has turned off simply doesn't appear here.
|
|
9
|
+
//
|
|
10
|
+
// Scope split: per-USER connections (a personal GitHub token, own-machine runners, personal
|
|
11
|
+
// subscriptions) now live in the "My setup" hub (UserMenu → My setup), NOT here — keeping
|
|
12
|
+
// this hub purely workspace-scoped. When auth is disabled there is no UserMenu to host them,
|
|
13
|
+
// so a "Personal (only you)" group falls back into this hub so they stay reachable.
|
|
10
14
|
const ui = useUiStore()
|
|
11
15
|
const auth = useAuthStore()
|
|
12
16
|
const github = useGitHubStore()
|
|
@@ -20,6 +24,11 @@ const userSecrets = useUserSecretsStore()
|
|
|
20
24
|
const apiKeys = useApiKeysStore()
|
|
21
25
|
const workspace = useWorkspaceStore()
|
|
22
26
|
|
|
27
|
+
// True when the per-user "My setup" hub is reachable (UserMenu renders only when signed in).
|
|
28
|
+
// When false (auth disabled / local mode) we fold the personal rows back into this hub so
|
|
29
|
+
// nothing becomes unreachable.
|
|
30
|
+
const personalHubReachable = computed(() => !!auth.user)
|
|
31
|
+
|
|
23
32
|
// The selected filing tracker, as a badge label ("GitHub Issues" / "Jira").
|
|
24
33
|
const trackerLabel = computed(() => {
|
|
25
34
|
if (tracker.settings.tracker === 'github') return 'GitHub Issues'
|
|
@@ -27,12 +36,17 @@ const trackerLabel = computed(() => {
|
|
|
27
36
|
return undefined
|
|
28
37
|
})
|
|
29
38
|
|
|
39
|
+
// Free-text filter over the rows (label + description), so a workspace with many enabled
|
|
40
|
+
// systems stays scannable. Reset when the hub re-opens.
|
|
41
|
+
const query = ref('')
|
|
42
|
+
|
|
30
43
|
// The observability connection status drives the hub's connected badge. Load it
|
|
31
44
|
// lazily when the hub opens (the secret-less connection view is cheap).
|
|
32
45
|
watch(
|
|
33
46
|
() => ui.integrationsOpen,
|
|
34
47
|
(isOpen) => {
|
|
35
48
|
if (isOpen) {
|
|
49
|
+
query.value = ''
|
|
36
50
|
void releaseHealth.ensureLoaded().catch(() => {})
|
|
37
51
|
void providerConnections.ensureLoaded().catch(() => {})
|
|
38
52
|
void userSecrets.load().catch(() => {})
|
|
@@ -47,8 +61,10 @@ const open = computed({
|
|
|
47
61
|
set: (v: boolean) => (v ? ui.openIntegrations() : ui.closeIntegrations()),
|
|
48
62
|
})
|
|
49
63
|
|
|
50
|
-
// One integration row. `
|
|
51
|
-
//
|
|
64
|
+
// One integration row. `connected` drives the green badge (`status` is its line — an
|
|
65
|
+
// account/team name or "Connected"); `attention` drives an amber badge (e.g. a source that
|
|
66
|
+
// is available but turned off) with `attentionLabel`. `recommended` tags an essential row
|
|
67
|
+
// with a "Recommended" chip while the workspace has nothing connected yet.
|
|
52
68
|
interface IntegrationItem {
|
|
53
69
|
key: string
|
|
54
70
|
icon: string
|
|
@@ -56,12 +72,27 @@ interface IntegrationItem {
|
|
|
56
72
|
description: string
|
|
57
73
|
status?: string
|
|
58
74
|
connected?: boolean
|
|
75
|
+
attention?: boolean
|
|
76
|
+
attentionLabel?: string
|
|
77
|
+
recommended?: boolean
|
|
78
|
+
onClick: () => void
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// A group may carry a small de-emphasised footer LINK (workspace config that isn't itself an
|
|
82
|
+
// integration, e.g. the issue-tracker settings) rendered under its rows rather than as a
|
|
83
|
+
// full row competing with the connections.
|
|
84
|
+
interface IntegrationFooterLink {
|
|
85
|
+
key: string
|
|
86
|
+
icon: string
|
|
87
|
+
label: string
|
|
88
|
+
status?: string
|
|
59
89
|
onClick: () => void
|
|
60
90
|
}
|
|
61
91
|
|
|
62
92
|
interface IntegrationGroup {
|
|
63
93
|
title: string
|
|
64
94
|
items: IntegrationItem[]
|
|
95
|
+
footerLink?: IntegrationFooterLink
|
|
65
96
|
}
|
|
66
97
|
|
|
67
98
|
// Run an integration's open handler, then dismiss the hub so its panel takes over.
|
|
@@ -87,22 +118,16 @@ const groups = computed<IntegrationGroup[]>(() => {
|
|
|
87
118
|
description: 'One gateway to 300+ models — add your key and enable models in one place.',
|
|
88
119
|
status: openRouterKeyConnected ? 'Key connected' : undefined,
|
|
89
120
|
connected: openRouterKeyConnected,
|
|
121
|
+
recommended: true,
|
|
90
122
|
onClick: () => go(ui.openOpenRouter),
|
|
91
123
|
},
|
|
92
124
|
{
|
|
93
125
|
key: 'vendors',
|
|
94
126
|
icon: 'i-lucide-key-round',
|
|
95
127
|
label: 'Vendors & keys',
|
|
96
|
-
description: 'LLM
|
|
128
|
+
description: 'Workspace LLM subscriptions and provider API keys.',
|
|
97
129
|
onClick: () => go(ui.openVendorCredentials),
|
|
98
130
|
},
|
|
99
|
-
{
|
|
100
|
-
key: 'local-runners',
|
|
101
|
-
icon: 'i-lucide-server',
|
|
102
|
-
label: 'My local runners',
|
|
103
|
-
description: 'Your own-machine model runners (Ollama, LM Studio, vLLM…).',
|
|
104
|
-
onClick: () => go(ui.openLocalModels),
|
|
105
|
-
},
|
|
106
131
|
],
|
|
107
132
|
})
|
|
108
133
|
|
|
@@ -116,23 +141,10 @@ const groups = computed<IntegrationGroup[]>(() => {
|
|
|
116
141
|
description: 'Connect the workspace’s GitHub App, browse repos, PRs and issues.',
|
|
117
142
|
status: github.connected ? github.connection?.accountLogin : undefined,
|
|
118
143
|
connected: github.connected,
|
|
144
|
+
recommended: true,
|
|
119
145
|
onClick: () => go(ui.openGitHub),
|
|
120
146
|
})
|
|
121
147
|
}
|
|
122
|
-
// Per-user GitHub PAT — works on every runtime (used for runs you initiate). Always
|
|
123
|
-
// offered; the badge reflects whether the signed-in user has stored one.
|
|
124
|
-
{
|
|
125
|
-
const pat = userSecrets.statusFor('github_pat')
|
|
126
|
-
code.push({
|
|
127
|
-
key: 'github-pat',
|
|
128
|
-
icon: 'i-lucide-key-round',
|
|
129
|
-
label: 'My GitHub token',
|
|
130
|
-
description: 'A personal access token used for runs you start (pushes, PRs, CI, merge).',
|
|
131
|
-
status: pat ? 'Connected' : undefined,
|
|
132
|
-
connected: !!pat,
|
|
133
|
-
onClick: () => go(ui.openUserSecrets),
|
|
134
|
-
})
|
|
135
|
-
}
|
|
136
148
|
if (code.length) out.push({ title: 'Source control', items: code })
|
|
137
149
|
|
|
138
150
|
// --- Communication ---------------------------------------------------------
|
|
@@ -180,10 +192,11 @@ const groups = computed<IntegrationGroup[]>(() => {
|
|
|
180
192
|
icon: src.icon,
|
|
181
193
|
label: src.label,
|
|
182
194
|
description: `Link ${src.label} to import and reference tracker issues.`,
|
|
183
|
-
// Available + enabled ⇒ offered (green); available + off ⇒ "Disabled";
|
|
195
|
+
// Available + enabled ⇒ offered (green); available + off ⇒ "Disabled" (amber);
|
|
184
196
|
// not available ⇒ no badge (Jira needs connecting; GitHub needs its App).
|
|
185
|
-
status: src.available ? (src.enabled ? undefined : 'Disabled') : undefined,
|
|
186
197
|
connected: src.available && src.enabled,
|
|
198
|
+
attention: src.available && !src.enabled,
|
|
199
|
+
attentionLabel: 'Disabled',
|
|
187
200
|
onClick: () => go(() => ui.openTaskConnect(src.source)),
|
|
188
201
|
}))
|
|
189
202
|
if (tasks.anyOffered) {
|
|
@@ -195,16 +208,19 @@ const groups = computed<IntegrationGroup[]>(() => {
|
|
|
195
208
|
onClick: () => go(() => ui.openTaskImport(null)),
|
|
196
209
|
})
|
|
197
210
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
211
|
+
// Choosing the filing tracker / writeback is workspace CONFIG, not an integration, so it
|
|
212
|
+
// sits as a quiet footer link under the sources rather than a competing row.
|
|
213
|
+
out.push({
|
|
214
|
+
title: 'Task trackers',
|
|
215
|
+
items: trackers,
|
|
216
|
+
footerLink: {
|
|
217
|
+
key: 'task:tracker',
|
|
218
|
+
icon: 'i-lucide-list-checks',
|
|
219
|
+
label: 'Issue tracker settings',
|
|
220
|
+
status: trackerLabel.value,
|
|
221
|
+
onClick: () => go(() => ui.openWorkspaceSettings('tracker')),
|
|
222
|
+
},
|
|
206
223
|
})
|
|
207
|
-
out.push({ title: 'Task trackers', items: trackers })
|
|
208
224
|
}
|
|
209
225
|
|
|
210
226
|
// --- Observability ---------------------------------------------------------
|
|
@@ -271,8 +287,73 @@ const groups = computed<IntegrationGroup[]>(() => {
|
|
|
271
287
|
}
|
|
272
288
|
if (infra.length) out.push({ title: 'Infrastructure', items: infra })
|
|
273
289
|
|
|
290
|
+
// --- Personal (only you) — fallback when there is no UserMenu to host "My setup" -------
|
|
291
|
+
// Per-user connections normally live in the My-setup hub; with auth disabled they fold in
|
|
292
|
+
// here so they stay reachable. (The badge reflects the signed-in user's stored secret.)
|
|
293
|
+
if (!personalHubReachable.value) {
|
|
294
|
+
const pat = !!userSecrets.statusFor('github_pat')
|
|
295
|
+
out.push({
|
|
296
|
+
title: 'Personal (only you)',
|
|
297
|
+
items: [
|
|
298
|
+
{
|
|
299
|
+
key: 'github-pat',
|
|
300
|
+
icon: 'i-lucide-key-round',
|
|
301
|
+
label: 'My GitHub token',
|
|
302
|
+
description: 'A personal access token used for runs you start (pushes, PRs, CI, merge).',
|
|
303
|
+
status: pat ? 'Connected' : undefined,
|
|
304
|
+
connected: pat,
|
|
305
|
+
onClick: () => go(ui.openUserSecrets),
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
key: 'local-runners',
|
|
309
|
+
icon: 'i-lucide-server',
|
|
310
|
+
label: 'My local runners',
|
|
311
|
+
description: 'Your own-machine model runners (Ollama, LM Studio, vLLM…).',
|
|
312
|
+
onClick: () => go(ui.openLocalModels),
|
|
313
|
+
},
|
|
314
|
+
],
|
|
315
|
+
})
|
|
316
|
+
}
|
|
317
|
+
|
|
274
318
|
return out
|
|
275
319
|
})
|
|
320
|
+
|
|
321
|
+
// Sort connected rows first, then amber "attention", then idle — a stable rank so each
|
|
322
|
+
// group reads "what's live" top-down without reshuffling unrelated rows.
|
|
323
|
+
function stateRank(item: IntegrationItem): number {
|
|
324
|
+
if (item.connected) return 0
|
|
325
|
+
if (item.attention) return 1
|
|
326
|
+
return 2
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const allItems = computed(() => groups.value.flatMap((g) => g.items))
|
|
330
|
+
const anyConnected = computed(() => allItems.value.some((i) => i.connected))
|
|
331
|
+
// Essential rows still unconnected — surfaced as the empty-workspace get-started shortcuts.
|
|
332
|
+
const recommendedActions = computed(() =>
|
|
333
|
+
allItems.value.filter((i) => i.recommended && !i.connected),
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
function matches(text: string, q: string): boolean {
|
|
337
|
+
return text.toLowerCase().includes(q)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Groups after the search filter + connected-first sort. A footer link is kept only when it
|
|
341
|
+
// also matches the query (or the query is empty); a group with no surviving rows/link drops.
|
|
342
|
+
const filteredGroups = computed<IntegrationGroup[]>(() => {
|
|
343
|
+
const q = query.value.trim().toLowerCase()
|
|
344
|
+
return groups.value
|
|
345
|
+
.map((g) => {
|
|
346
|
+
const items = (
|
|
347
|
+
q ? g.items.filter((i) => matches(i.label, q) || matches(i.description, q)) : g.items
|
|
348
|
+
)
|
|
349
|
+
.slice()
|
|
350
|
+
.sort((a, b) => stateRank(a) - stateRank(b))
|
|
351
|
+
const footerLink =
|
|
352
|
+
g.footerLink && (!q || matches(g.footerLink.label, q)) ? g.footerLink : undefined
|
|
353
|
+
return { ...g, items, footerLink }
|
|
354
|
+
})
|
|
355
|
+
.filter((g) => g.items.length || g.footerLink)
|
|
356
|
+
})
|
|
276
357
|
</script>
|
|
277
358
|
|
|
278
359
|
<template>
|
|
@@ -283,7 +364,47 @@ const groups = computed<IntegrationGroup[]>(() => {
|
|
|
283
364
|
Connect and manage the external systems this workspace can link in.
|
|
284
365
|
</p>
|
|
285
366
|
|
|
286
|
-
|
|
367
|
+
<!-- Get-started cue: an empty workspace gets the two essentials up front so the first
|
|
368
|
+
run isn't blocked on hunting for them. Hidden once anything is connected. -->
|
|
369
|
+
<div
|
|
370
|
+
v-if="!anyConnected && recommendedActions.length"
|
|
371
|
+
class="rounded-lg border border-primary-500/40 bg-primary-500/10 p-3"
|
|
372
|
+
>
|
|
373
|
+
<div class="mb-2 flex items-center gap-2 text-sm font-medium text-primary-200">
|
|
374
|
+
<UIcon name="i-lucide-rocket" class="h-4 w-4 shrink-0" />
|
|
375
|
+
<span>Get started</span>
|
|
376
|
+
</div>
|
|
377
|
+
<p class="mb-3 text-xs text-slate-300">
|
|
378
|
+
Connect a code source and a model provider to run your first pipeline.
|
|
379
|
+
</p>
|
|
380
|
+
<div class="flex flex-wrap gap-2">
|
|
381
|
+
<UButton
|
|
382
|
+
v-for="item in recommendedActions"
|
|
383
|
+
:key="`rec:${item.key}`"
|
|
384
|
+
size="xs"
|
|
385
|
+
color="primary"
|
|
386
|
+
variant="soft"
|
|
387
|
+
:icon="item.icon"
|
|
388
|
+
@click="item.onClick()"
|
|
389
|
+
>
|
|
390
|
+
{{ item.label }}
|
|
391
|
+
</UButton>
|
|
392
|
+
</div>
|
|
393
|
+
</div>
|
|
394
|
+
|
|
395
|
+
<UInput
|
|
396
|
+
v-model="query"
|
|
397
|
+
icon="i-lucide-search"
|
|
398
|
+
size="sm"
|
|
399
|
+
placeholder="Search integrations…"
|
|
400
|
+
class="w-full"
|
|
401
|
+
/>
|
|
402
|
+
|
|
403
|
+
<p v-if="!filteredGroups.length" class="px-1 py-6 text-center text-sm text-slate-500">
|
|
404
|
+
No integrations match “{{ query }}”.
|
|
405
|
+
</p>
|
|
406
|
+
|
|
407
|
+
<section v-for="group in filteredGroups" :key="group.title">
|
|
287
408
|
<h3 class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
288
409
|
{{ group.title }}
|
|
289
410
|
</h3>
|
|
@@ -302,12 +423,39 @@ const groups = computed<IntegrationGroup[]>(() => {
|
|
|
302
423
|
<UBadge v-if="item.connected" color="success" variant="subtle" size="sm">
|
|
303
424
|
{{ item.status || 'Connected' }}
|
|
304
425
|
</UBadge>
|
|
426
|
+
<UBadge v-else-if="item.attention" color="warning" variant="subtle" size="sm">
|
|
427
|
+
{{ item.attentionLabel || 'Needs attention' }}
|
|
428
|
+
</UBadge>
|
|
429
|
+
<span v-else class="text-[11px] text-slate-500">Not connected</span>
|
|
430
|
+
<UBadge
|
|
431
|
+
v-if="!anyConnected && item.recommended && !item.connected"
|
|
432
|
+
color="primary"
|
|
433
|
+
variant="subtle"
|
|
434
|
+
size="sm"
|
|
435
|
+
>
|
|
436
|
+
Recommended
|
|
437
|
+
</UBadge>
|
|
305
438
|
</div>
|
|
306
439
|
<p class="truncate text-xs text-slate-400">{{ item.description }}</p>
|
|
307
440
|
</div>
|
|
308
441
|
<UIcon name="i-lucide-chevron-right" class="h-4 w-4 shrink-0 text-slate-500" />
|
|
309
442
|
</button>
|
|
310
443
|
</div>
|
|
444
|
+
|
|
445
|
+
<!-- De-emphasised workspace-config link (e.g. issue tracker settings). -->
|
|
446
|
+
<button
|
|
447
|
+
v-if="group.footerLink"
|
|
448
|
+
type="button"
|
|
449
|
+
class="mt-1.5 flex w-full items-center gap-2 rounded-md px-3 py-1.5 text-left text-xs text-slate-400 transition hover:bg-slate-900/60 hover:text-slate-200"
|
|
450
|
+
@click="group.footerLink.onClick()"
|
|
451
|
+
>
|
|
452
|
+
<UIcon :name="group.footerLink.icon" class="h-3.5 w-3.5 shrink-0" />
|
|
453
|
+
<span class="flex-1 truncate">{{ group.footerLink.label }}</span>
|
|
454
|
+
<span v-if="group.footerLink.status" class="shrink-0 text-slate-500">{{
|
|
455
|
+
group.footerLink.status
|
|
456
|
+
}}</span>
|
|
457
|
+
<UIcon name="i-lucide-chevron-right" class="h-3.5 w-3.5 shrink-0 text-slate-600" />
|
|
458
|
+
</button>
|
|
311
459
|
</section>
|
|
312
460
|
</div>
|
|
313
461
|
</template>
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// "My setup": the user-scoped sibling of the Integrations hub. It lists the connections
|
|
3
|
+
// that belong to the SIGNED-IN USER rather than the workspace — a personal GitHub token,
|
|
4
|
+
// own-machine local model runners, and individual-usage (personal) subscriptions — which
|
|
5
|
+
// previously sat, confusingly, among the workspace-wide integrations. Each row reuses the
|
|
6
|
+
// existing per-panel handlers via `ui.openFromPersonal(...)`, so opening one closes this
|
|
7
|
+
// hub, reveals that panel, and gives it a "Back to My setup" control (IntegrationBackTitle).
|
|
8
|
+
const ui = useUiStore()
|
|
9
|
+
const userSecrets = useUserSecretsStore()
|
|
10
|
+
const localModels = useLocalModelsStore()
|
|
11
|
+
const personalSubs = usePersonalSubscriptionsStore()
|
|
12
|
+
|
|
13
|
+
// Load the cheap user-scoped status whenever the hub opens, so each row's badge is accurate.
|
|
14
|
+
watch(
|
|
15
|
+
() => ui.personalSetupOpen,
|
|
16
|
+
(isOpen) => {
|
|
17
|
+
if (!isOpen) return
|
|
18
|
+
void userSecrets.load().catch(() => {})
|
|
19
|
+
void localModels.load().catch(() => {})
|
|
20
|
+
void personalSubs.load().catch(() => {})
|
|
21
|
+
},
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
const open = computed({
|
|
25
|
+
get: () => ui.personalSetupOpen,
|
|
26
|
+
set: (v: boolean) => (v ? ui.openPersonalSetup() : ui.closePersonalSetup()),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
// One row. `connected` drives the badge; `status` is the connected-state line (a count or
|
|
30
|
+
// "Connected"). Mirrors the Integrations hub's row shape so the two hubs look identical.
|
|
31
|
+
interface PersonalItem {
|
|
32
|
+
key: string
|
|
33
|
+
icon: string
|
|
34
|
+
label: string
|
|
35
|
+
description: string
|
|
36
|
+
status?: string
|
|
37
|
+
connected: boolean
|
|
38
|
+
onClick: () => void
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface PersonalGroup {
|
|
42
|
+
title: string
|
|
43
|
+
items: PersonalItem[]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Open a user-scoped panel from this hub (sets the "came from My setup" marker).
|
|
47
|
+
function go(fn: () => void) {
|
|
48
|
+
ui.openFromPersonal(fn)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const groups = computed<PersonalGroup[]>(() => {
|
|
52
|
+
const out: PersonalGroup[] = []
|
|
53
|
+
|
|
54
|
+
// --- Source control --------------------------------------------------------
|
|
55
|
+
const pat = !!userSecrets.statusFor('github_pat')
|
|
56
|
+
out.push({
|
|
57
|
+
title: 'Source control',
|
|
58
|
+
items: [
|
|
59
|
+
{
|
|
60
|
+
key: 'github-pat',
|
|
61
|
+
icon: 'i-lucide-key-round',
|
|
62
|
+
label: 'My GitHub token',
|
|
63
|
+
description: 'A personal access token used for runs you start (pushes, PRs, CI, merge).',
|
|
64
|
+
status: pat ? 'Connected' : undefined,
|
|
65
|
+
connected: pat,
|
|
66
|
+
onClick: () => go(ui.openUserSecrets),
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
// --- Models ----------------------------------------------------------------
|
|
72
|
+
const runnerCount = localModels.endpoints.length
|
|
73
|
+
const subCount = personalSubs.subscriptions.length
|
|
74
|
+
out.push({
|
|
75
|
+
title: 'Models',
|
|
76
|
+
items: [
|
|
77
|
+
{
|
|
78
|
+
key: 'local-runners',
|
|
79
|
+
icon: 'i-lucide-server',
|
|
80
|
+
label: 'My local runners',
|
|
81
|
+
description: 'Your own-machine model runners (Ollama, LM Studio, vLLM…).',
|
|
82
|
+
status: runnerCount ? `${runnerCount} connected` : undefined,
|
|
83
|
+
connected: runnerCount > 0,
|
|
84
|
+
onClick: () => go(ui.openLocalModels),
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
key: 'personal-subs',
|
|
88
|
+
icon: 'i-lucide-user',
|
|
89
|
+
label: 'My subscriptions',
|
|
90
|
+
description:
|
|
91
|
+
'Individual-usage plans you unlock with a personal password (Claude, GLM, Codex).',
|
|
92
|
+
status: subCount ? `${subCount} connected` : undefined,
|
|
93
|
+
connected: subCount > 0,
|
|
94
|
+
onClick: () => go(() => ui.openVendorCredentials('personal')),
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
return out
|
|
100
|
+
})
|
|
101
|
+
</script>
|
|
102
|
+
|
|
103
|
+
<template>
|
|
104
|
+
<UModal v-model:open="open" title="My setup" :ui="{ content: 'max-w-xl' }">
|
|
105
|
+
<template #body>
|
|
106
|
+
<div class="space-y-5">
|
|
107
|
+
<p class="text-xs text-slate-400">
|
|
108
|
+
Your personal connections — used for runs you start, and visible only to you.
|
|
109
|
+
</p>
|
|
110
|
+
|
|
111
|
+
<section v-for="group in groups" :key="group.title">
|
|
112
|
+
<h3 class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
113
|
+
{{ group.title }}
|
|
114
|
+
</h3>
|
|
115
|
+
<div class="space-y-1.5">
|
|
116
|
+
<button
|
|
117
|
+
v-for="item in group.items"
|
|
118
|
+
:key="item.key"
|
|
119
|
+
type="button"
|
|
120
|
+
class="flex w-full items-center gap-3 rounded-lg border border-slate-800 bg-slate-900/50 px-3 py-2.5 text-left transition hover:border-slate-700 hover:bg-slate-900"
|
|
121
|
+
@click="item.onClick()"
|
|
122
|
+
>
|
|
123
|
+
<UIcon :name="item.icon" class="h-5 w-5 shrink-0 text-slate-300" />
|
|
124
|
+
<div class="min-w-0 flex-1">
|
|
125
|
+
<div class="flex items-center gap-2">
|
|
126
|
+
<span class="truncate text-sm font-medium text-slate-100">{{ item.label }}</span>
|
|
127
|
+
<UBadge v-if="item.connected" color="success" variant="subtle" size="sm">
|
|
128
|
+
{{ item.status || 'Connected' }}
|
|
129
|
+
</UBadge>
|
|
130
|
+
<span v-else class="text-[11px] text-slate-500">Not connected</span>
|
|
131
|
+
</div>
|
|
132
|
+
<p class="truncate text-xs text-slate-400">{{ item.description }}</p>
|
|
133
|
+
</div>
|
|
134
|
+
<UIcon name="i-lucide-chevron-right" class="h-4 w-4 shrink-0 text-slate-500" />
|
|
135
|
+
</button>
|
|
136
|
+
</div>
|
|
137
|
+
</section>
|
|
138
|
+
</div>
|
|
139
|
+
</template>
|
|
140
|
+
</UModal>
|
|
141
|
+
</template>
|
|
@@ -31,7 +31,7 @@ function toggleGating(i: number) {
|
|
|
31
31
|
if (!cfg) return
|
|
32
32
|
cfg.gating = cfg.gating?.enabled
|
|
33
33
|
? { ...cfg.gating, enabled: false }
|
|
34
|
-
: { enabled: true, minRisk: 0.6, minImpact: 0.6 }
|
|
34
|
+
: { enabled: true, minRisk: 0.6, minImpact: 0.6, onMissingEstimate: 'consensus' }
|
|
35
35
|
}
|
|
36
36
|
const agents = useAgentsStore()
|
|
37
37
|
const ui = useUiStore()
|
|
@@ -22,7 +22,9 @@ const back = useIntegrationBack(open)
|
|
|
22
22
|
|
|
23
23
|
// Horizontal tabs replace the old long vertical scroll: each credential kind is its own
|
|
24
24
|
// section (pooled subscriptions, direct vendor keys, proxy/gateway keys, personal subs).
|
|
25
|
-
|
|
25
|
+
// Initialised from the ui store so a caller can deep-link to a tab — the user-scoped
|
|
26
|
+
// "My subscriptions" entry opens straight onto the `personal` tab.
|
|
27
|
+
const activeTab = ref(ui.vendorCredentialsTab)
|
|
26
28
|
const tabs = [
|
|
27
29
|
{ value: 'pool', label: 'Workspace pool', icon: 'i-lucide-users', slot: 'pool' },
|
|
28
30
|
{ value: 'direct', label: 'Direct providers', icon: 'i-lucide-key-round', slot: 'direct' },
|
|
@@ -51,7 +53,10 @@ const token = ref('')
|
|
|
51
53
|
const busy = ref(false)
|
|
52
54
|
|
|
53
55
|
watch(open, (isOpen) => {
|
|
54
|
-
if (isOpen
|
|
56
|
+
if (!isOpen) return
|
|
57
|
+
// Honour a deep-linked tab each time the modal opens (e.g. "My subscriptions" → personal).
|
|
58
|
+
activeTab.value = ui.vendorCredentialsTab
|
|
59
|
+
if (workspace.workspaceId) void creds.load(workspace.workspaceId)
|
|
55
60
|
})
|
|
56
61
|
|
|
57
62
|
/** Step-by-step instructions for the selected vendor. */
|