@cat-factory/app 0.6.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/LICENSE +21 -0
- package/README.md +88 -0
- package/app/app.config.ts +8 -0
- package/app/app.vue +11 -0
- package/app/assets/css/main.css +100 -0
- package/app/components/auth/AuthGate.vue +24 -0
- package/app/components/auth/LoginScreen.vue +143 -0
- package/app/components/auth/UserMenu.vue +39 -0
- package/app/components/board/AddTaskModal.vue +444 -0
- package/app/components/board/AgentFailureCard.vue +97 -0
- package/app/components/board/AgentStopButton.vue +61 -0
- package/app/components/board/BoardCanvas.vue +183 -0
- package/app/components/board/ContextPicker.vue +367 -0
- package/app/components/board/RecurringPipelineModal.vue +219 -0
- package/app/components/board/TaskDependencyEdges.vue +132 -0
- package/app/components/board/nodes/AgentChip.vue +59 -0
- package/app/components/board/nodes/BlockNode.vue +433 -0
- package/app/components/board/nodes/DecisionBadge.vue +27 -0
- package/app/components/board/nodes/DraggableTask.vue +48 -0
- package/app/components/board/nodes/ModuleFrame.vue +97 -0
- package/app/components/board/nodes/TaskCard.vue +359 -0
- package/app/components/board/nodes/TaskPipelineMini.vue +159 -0
- package/app/components/bootstrap/BootstrapModal.vue +665 -0
- package/app/components/clarity/ClarityReviewWindow.vue +611 -0
- package/app/components/consensus/ConsensusSessionWindow.vue +210 -0
- package/app/components/documents/DocumentImportModal.vue +161 -0
- package/app/components/documents/DocumentSourceConnectModal.vue +127 -0
- package/app/components/documents/SpawnPreviewModal.vue +161 -0
- package/app/components/documents/TaskContextDocs.vue +83 -0
- package/app/components/focus/BlockFocusView.vue +171 -0
- package/app/components/fragments/FragmentLibraryPanel.vue +340 -0
- package/app/components/gates/GateResultView.vue +282 -0
- package/app/components/github/AddServiceFromRepoModal.vue +354 -0
- package/app/components/github/GitHubConnect.vue +183 -0
- package/app/components/github/GitHubOnboarding.vue +45 -0
- package/app/components/github/GitHubPanel.vue +584 -0
- package/app/components/github/RepoTreeBrowser.vue +171 -0
- package/app/components/layout/AccountTeamSettings.vue +237 -0
- package/app/components/layout/BoardSwitcher.vue +280 -0
- package/app/components/layout/BoardToolbar.vue +156 -0
- package/app/components/layout/CommandBar.vue +336 -0
- package/app/components/layout/GitHubPatBanner.vue +73 -0
- package/app/components/layout/NotificationsInbox.vue +175 -0
- package/app/components/layout/SideBar.vue +314 -0
- package/app/components/layout/SpendWarningBanner.vue +107 -0
- package/app/components/observability/StepMetricsBar.vue +102 -0
- package/app/components/palettes/AgentPalette.vue +86 -0
- package/app/components/panels/AgentStepDetail.vue +737 -0
- package/app/components/panels/DecisionModal.vue +71 -0
- package/app/components/panels/InspectorPanel.vue +465 -0
- package/app/components/panels/ObservabilityPanel.vue +351 -0
- package/app/components/panels/StepMetadataCard.vue +253 -0
- package/app/components/panels/StepRestartControl.vue +90 -0
- package/app/components/panels/StepResultViewHost.vue +40 -0
- package/app/components/panels/StepTestReport.vue +84 -0
- package/app/components/panels/inspector/ContainerSummary.vue +74 -0
- package/app/components/panels/inspector/RecurringScheduleSettings.vue +178 -0
- package/app/components/panels/inspector/ServiceFragments.vue +82 -0
- package/app/components/panels/inspector/ServiceTestConfig.vue +198 -0
- package/app/components/panels/inspector/TaskAgentConfig.vue +81 -0
- package/app/components/panels/inspector/TaskDependencies.vue +70 -0
- package/app/components/panels/inspector/TaskEstimateBadge.vue +56 -0
- package/app/components/panels/inspector/TaskExecution.vue +364 -0
- package/app/components/panels/inspector/TaskRunSettings.vue +187 -0
- package/app/components/panels/inspector/TaskStructure.vue +96 -0
- package/app/components/pipeline/AgentKindIcon.vue +30 -0
- package/app/components/pipeline/IterationCapPrompt.vue +70 -0
- package/app/components/pipeline/PipelineBuilder.vue +817 -0
- package/app/components/pipeline/PipelineProgress.vue +484 -0
- package/app/components/providers/ApiKeysSection.vue +273 -0
- package/app/components/providers/PersonalCredentialModal.vue +128 -0
- package/app/components/providers/PersonalSubscriptionSection.vue +225 -0
- package/app/components/providers/VendorCredentialsModal.vue +197 -0
- package/app/components/recurring/RecurrenceEditor.vue +124 -0
- package/app/components/requirements/RequirementsReviewWindow.vue +620 -0
- package/app/components/settings/DatadogPanel.vue +213 -0
- package/app/components/settings/LocalModelEndpointsPanel.vue +286 -0
- package/app/components/settings/MergeThresholdsPanel.vue +378 -0
- package/app/components/settings/ModelDefaultsPanel.vue +250 -0
- package/app/components/settings/ServiceFragmentDefaultsPanel.vue +124 -0
- package/app/components/settings/WorkspaceSettingsPanel.vue +142 -0
- package/app/components/slack/SlackPanel.vue +299 -0
- package/app/components/tasks/TaskContextIssues.vue +88 -0
- package/app/components/tasks/TaskImportModal.vue +207 -0
- package/app/components/tasks/TaskSourceConnectModal.vue +133 -0
- package/app/components/testing/TestReportWindow.vue +404 -0
- package/app/composables/api/accounts.ts +81 -0
- package/app/composables/api/auth.ts +45 -0
- package/app/composables/api/board.ts +101 -0
- package/app/composables/api/bootstrap.ts +62 -0
- package/app/composables/api/context.ts +25 -0
- package/app/composables/api/documents.ts +74 -0
- package/app/composables/api/execution.ts +127 -0
- package/app/composables/api/fragments.ts +71 -0
- package/app/composables/api/github.ts +131 -0
- package/app/composables/api/models.ts +127 -0
- package/app/composables/api/notifications.ts +23 -0
- package/app/composables/api/presets.ts +29 -0
- package/app/composables/api/recurring.ts +68 -0
- package/app/composables/api/releaseHealth.ts +43 -0
- package/app/composables/api/reviews.ts +146 -0
- package/app/composables/api/slack.ts +54 -0
- package/app/composables/api/tasks.ts +72 -0
- package/app/composables/api/workspaces.ts +36 -0
- package/app/composables/useApi.ts +89 -0
- package/app/composables/useBlockDrag.ts +90 -0
- package/app/composables/useBlockQueries.ts +154 -0
- package/app/composables/useBoardFlow.ts +11 -0
- package/app/composables/useContextLinking.ts +65 -0
- package/app/composables/useDepLabels.ts +26 -0
- package/app/composables/useFrameResize.ts +54 -0
- package/app/composables/useResultView.ts +48 -0
- package/app/composables/useReviewStage.ts +40 -0
- package/app/composables/useSemanticZoom.ts +31 -0
- package/app/composables/useStepApproval.ts +233 -0
- package/app/composables/useStepProse.ts +78 -0
- package/app/composables/useStepTimer.ts +63 -0
- package/app/composables/useTaskExpansion.ts +92 -0
- package/app/composables/useWorkspaceStream.ts +155 -0
- package/app/docs/architecture.md +31 -0
- package/app/pages/index.vue +141 -0
- package/app/stores/accounts.ts +152 -0
- package/app/stores/agentConfig.ts +35 -0
- package/app/stores/agentRuns.ts +122 -0
- package/app/stores/agents.ts +40 -0
- package/app/stores/apiKeys.ts +108 -0
- package/app/stores/auth.ts +166 -0
- package/app/stores/board.spec.ts +205 -0
- package/app/stores/board.ts +286 -0
- package/app/stores/bootstrap.ts +97 -0
- package/app/stores/clarity.ts +196 -0
- package/app/stores/consensus.ts +60 -0
- package/app/stores/documents.ts +176 -0
- package/app/stores/execution.ts +273 -0
- package/app/stores/fragmentLibrary.ts +147 -0
- package/app/stores/fragments.ts +40 -0
- package/app/stores/github.ts +305 -0
- package/app/stores/localModels.ts +51 -0
- package/app/stores/mergePresets.ts +58 -0
- package/app/stores/modelDefaults.ts +76 -0
- package/app/stores/models.ts +134 -0
- package/app/stores/notifications.ts +70 -0
- package/app/stores/observability.ts +144 -0
- package/app/stores/personalSubscriptions.ts +215 -0
- package/app/stores/pipelines.ts +327 -0
- package/app/stores/recurringPipelines.ts +112 -0
- package/app/stores/releaseHealth.ts +75 -0
- package/app/stores/requirements.spec.ts +94 -0
- package/app/stores/requirements.ts +208 -0
- package/app/stores/serviceFragmentDefaults.ts +29 -0
- package/app/stores/services.ts +87 -0
- package/app/stores/slack.ts +142 -0
- package/app/stores/taskExpansion.ts +36 -0
- package/app/stores/tasks.spec.ts +71 -0
- package/app/stores/tasks.ts +176 -0
- package/app/stores/tracker.ts +27 -0
- package/app/stores/ui.ts +434 -0
- package/app/stores/vendorCredentials.ts +54 -0
- package/app/stores/workspace.ts +215 -0
- package/app/stores/workspaceSettings.ts +36 -0
- package/app/types/accounts.ts +77 -0
- package/app/types/bootstrap.ts +83 -0
- package/app/types/clarity.ts +59 -0
- package/app/types/consensus.ts +91 -0
- package/app/types/documents.ts +104 -0
- package/app/types/domain.ts +495 -0
- package/app/types/execution.ts +383 -0
- package/app/types/fragments.ts +72 -0
- package/app/types/github.ts +173 -0
- package/app/types/localModels.ts +73 -0
- package/app/types/merge.ts +71 -0
- package/app/types/models.ts +157 -0
- package/app/types/notifications.ts +74 -0
- package/app/types/recurring.ts +69 -0
- package/app/types/releaseHealth.ts +31 -0
- package/app/types/requirements.ts +61 -0
- package/app/types/services.ts +27 -0
- package/app/types/slack.ts +57 -0
- package/app/types/tasks.ts +82 -0
- package/app/types/tracker.ts +18 -0
- package/app/utils/agentOutput.spec.ts +128 -0
- package/app/utils/agentOutput.ts +173 -0
- package/app/utils/catalog.spec.ts +112 -0
- package/app/utils/catalog.ts +455 -0
- package/app/utils/dnd.ts +29 -0
- package/app/utils/observability.ts +52 -0
- package/app/utils/pipelineRender.ts +151 -0
- package/nuxt.config.ts +55 -0
- package/package.json +45 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { Notification } from '~/types/domain'
|
|
3
|
+
|
|
4
|
+
// The board's notification inbox: a bell with an open-count badge that opens a
|
|
5
|
+
// panel of human-actionable items (a PR awaiting a merge decision, a completed
|
|
6
|
+
// pipeline awaiting confirmation, CI that gave up). Each item can be acted on
|
|
7
|
+
// (merge / confirm / retry) or dismissed. Hydrated from the snapshot and patched
|
|
8
|
+
// live via the `notification` WorkspaceEvent.
|
|
9
|
+
|
|
10
|
+
const notifications = useNotificationsStore()
|
|
11
|
+
const ui = useUiStore()
|
|
12
|
+
const execution = useExecutionStore()
|
|
13
|
+
|
|
14
|
+
const busy = ref<string | null>(null)
|
|
15
|
+
|
|
16
|
+
/** Per-type display metadata (icon, colour, primary-action label). */
|
|
17
|
+
type Accent = 'warning' | 'primary' | 'error'
|
|
18
|
+
const META: Record<Notification['type'], { icon: string; color: Accent; action: string }> = {
|
|
19
|
+
merge_review: { icon: 'i-lucide-git-pull-request-arrow', color: 'warning', action: 'Merge' },
|
|
20
|
+
pipeline_complete: { icon: 'i-lucide-circle-check', color: 'primary', action: 'Confirm & merge' },
|
|
21
|
+
ci_failed: { icon: 'i-lucide-triangle-alert', color: 'error', action: 'Retry run' },
|
|
22
|
+
test_failed: { icon: 'i-lucide-flask-conical', color: 'error', action: 'Retry run' },
|
|
23
|
+
// Clicking the title opens the review window for the task (see `reveal`); "act" just marks
|
|
24
|
+
// it read (the server performs no side-effect for this type).
|
|
25
|
+
requirement_review: { icon: 'i-lucide-clipboard-list', color: 'primary', action: 'Mark read' },
|
|
26
|
+
// Clicking the title opens the clarity review window for the task (see `reveal`); "act"
|
|
27
|
+
// just marks it read (the server performs no side-effect for this type).
|
|
28
|
+
clarity_review: { icon: 'i-lucide-bug', color: 'primary', action: 'Mark read' },
|
|
29
|
+
// A post-release Datadog regression the on-call agent investigated. The human decides
|
|
30
|
+
// whether to revert (in GitHub via the PR link) or acknowledge; "act" marks it handled.
|
|
31
|
+
release_regression: { icon: 'i-lucide-activity', color: 'error', action: 'Acknowledge' },
|
|
32
|
+
// Clicking the title opens the parked step's decision surface (companion → step detail
|
|
33
|
+
// with the iteration-cap prompt; requirements → the review window); "act" just marks it
|
|
34
|
+
// read (the decision itself is resolved in that surface, not here).
|
|
35
|
+
decision_required: { icon: 'i-lucide-circle-help', color: 'warning', action: 'Mark read' },
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A notification the escalation sweep has flagged as overdue (waited past the threshold). */
|
|
39
|
+
function isUrgent(n: Notification): boolean {
|
|
40
|
+
return n.severity === 'urgent'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** True when any open notification is overdue — turns the toolbar bell red. */
|
|
44
|
+
const hasUrgent = computed(() => notifications.open.some(isUrgent))
|
|
45
|
+
|
|
46
|
+
/** Effective accent: urgent (red) overrides the type's base colour to convey "overdue". */
|
|
47
|
+
function accent(n: Notification): Accent {
|
|
48
|
+
return isUrgent(n) ? 'error' : META[n.type].color
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function act(n: Notification) {
|
|
52
|
+
busy.value = n.id
|
|
53
|
+
try {
|
|
54
|
+
await notifications.act(n.id)
|
|
55
|
+
} finally {
|
|
56
|
+
busy.value = null
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function dismiss(n: Notification) {
|
|
61
|
+
busy.value = n.id
|
|
62
|
+
try {
|
|
63
|
+
await notifications.dismiss(n.id)
|
|
64
|
+
} finally {
|
|
65
|
+
busy.value = null
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Clicking a notification's title takes the user where they can act on it. A
|
|
71
|
+
* `requirement_review` / `clarity_review` summons them straight back into the matching review
|
|
72
|
+
* window (the async incorporate/re-review raised new findings or hit the cap); every other
|
|
73
|
+
* type just focuses the related block on the board.
|
|
74
|
+
*/
|
|
75
|
+
function reveal(n: Notification) {
|
|
76
|
+
if (!n.blockId) return
|
|
77
|
+
if (n.type === 'requirement_review') ui.openRequirementReview(n.blockId)
|
|
78
|
+
else if (n.type === 'clarity_review') ui.openClarityReview(n.blockId)
|
|
79
|
+
else if (n.type === 'decision_required') revealDecision(n)
|
|
80
|
+
else ui.select(n.blockId)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Open the decision surface for a parked iteration-cap run: find the run's step that is
|
|
85
|
+
* waiting on a human and open it through the universal step dispatch — which routes a
|
|
86
|
+
* `requirements-review` step to the review window and a companion step to its detail
|
|
87
|
+
* panel (where the iteration-cap prompt lives). Falls back to focusing the block.
|
|
88
|
+
*/
|
|
89
|
+
function revealDecision(n: Notification) {
|
|
90
|
+
const instance = n.executionId ? execution.getInstance(n.executionId) : undefined
|
|
91
|
+
const idx = instance?.steps.findIndex((s) => s.state === 'waiting_decision') ?? -1
|
|
92
|
+
if (instance && idx >= 0) ui.openStepDetail(instance.id, idx)
|
|
93
|
+
else if (n.blockId) ui.select(n.blockId)
|
|
94
|
+
}
|
|
95
|
+
</script>
|
|
96
|
+
|
|
97
|
+
<template>
|
|
98
|
+
<UPopover v-if="notifications.count" :content="{ align: 'end' }">
|
|
99
|
+
<UButton :color="hasUrgent ? 'error' : 'warning'" variant="soft" size="sm" icon="i-lucide-bell">
|
|
100
|
+
{{ notifications.count }}
|
|
101
|
+
</UButton>
|
|
102
|
+
|
|
103
|
+
<template #content>
|
|
104
|
+
<div class="max-h-[28rem] w-96 overflow-y-auto p-2">
|
|
105
|
+
<div class="px-2 py-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
106
|
+
Needs your attention
|
|
107
|
+
</div>
|
|
108
|
+
<div
|
|
109
|
+
v-for="n in notifications.open"
|
|
110
|
+
:key="n.id"
|
|
111
|
+
class="rounded-lg border p-2.5 mt-1.5"
|
|
112
|
+
:class="
|
|
113
|
+
isUrgent(n)
|
|
114
|
+
? 'border-error-500/60 bg-error-500/10'
|
|
115
|
+
: 'border-slate-700/60 bg-slate-800/40'
|
|
116
|
+
"
|
|
117
|
+
>
|
|
118
|
+
<div class="flex items-start gap-2">
|
|
119
|
+
<UIcon
|
|
120
|
+
:name="META[n.type].icon"
|
|
121
|
+
:class="`mt-0.5 h-4 w-4 text-${accent(n)}-400 shrink-0`"
|
|
122
|
+
/>
|
|
123
|
+
<div class="min-w-0 flex-1">
|
|
124
|
+
<div class="flex items-center gap-1.5">
|
|
125
|
+
<button
|
|
126
|
+
class="block min-w-0 flex-1 truncate text-left text-sm font-medium text-slate-200 hover:underline"
|
|
127
|
+
:title="n.title"
|
|
128
|
+
@click="reveal(n)"
|
|
129
|
+
>
|
|
130
|
+
{{ n.title }}
|
|
131
|
+
</button>
|
|
132
|
+
<span
|
|
133
|
+
v-if="isUrgent(n)"
|
|
134
|
+
class="shrink-0 rounded bg-error-500/20 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-error-400"
|
|
135
|
+
>
|
|
136
|
+
Overdue
|
|
137
|
+
</span>
|
|
138
|
+
</div>
|
|
139
|
+
<p class="mt-0.5 text-[11px] leading-snug text-slate-400">{{ n.body }}</p>
|
|
140
|
+
<a
|
|
141
|
+
v-if="n.payload?.prUrl"
|
|
142
|
+
:href="n.payload.prUrl"
|
|
143
|
+
target="_blank"
|
|
144
|
+
rel="noopener"
|
|
145
|
+
class="mt-1 inline-flex items-center gap-1 text-[11px] text-sky-400 hover:underline"
|
|
146
|
+
>
|
|
147
|
+
<UIcon name="i-lucide-external-link" class="h-3 w-3" /> Open PR
|
|
148
|
+
</a>
|
|
149
|
+
<div class="mt-2 flex items-center gap-1.5">
|
|
150
|
+
<UButton
|
|
151
|
+
:color="accent(n)"
|
|
152
|
+
variant="soft"
|
|
153
|
+
size="xs"
|
|
154
|
+
:loading="busy === n.id"
|
|
155
|
+
@click="act(n)"
|
|
156
|
+
>
|
|
157
|
+
{{ META[n.type].action }}
|
|
158
|
+
</UButton>
|
|
159
|
+
<UButton
|
|
160
|
+
color="neutral"
|
|
161
|
+
variant="ghost"
|
|
162
|
+
size="xs"
|
|
163
|
+
:disabled="busy === n.id"
|
|
164
|
+
@click="dismiss(n)"
|
|
165
|
+
>
|
|
166
|
+
Dismiss
|
|
167
|
+
</UButton>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
</template>
|
|
174
|
+
</UPopover>
|
|
175
|
+
</template>
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// The left navbar. The old draggable block/pipeline palettes are gone — blocks
|
|
3
|
+
// and pipelines are created through the command bar (⌘K) and the board's own
|
|
4
|
+
// affordances. This panel is now navigation + a command-bar launcher: quick
|
|
5
|
+
// actions, repository management, integration management, the workspace-wide
|
|
6
|
+
// context-fragment library, and workspace configuration (merge thresholds +
|
|
7
|
+
// default models).
|
|
8
|
+
import BoardSwitcher from '~/components/layout/BoardSwitcher.vue'
|
|
9
|
+
import UserMenu from '~/components/auth/UserMenu.vue'
|
|
10
|
+
|
|
11
|
+
const documents = useDocumentsStore()
|
|
12
|
+
const tasks = useTasksStore()
|
|
13
|
+
const github = useGitHubStore()
|
|
14
|
+
const slack = useSlackStore()
|
|
15
|
+
const library = useFragmentLibraryStore()
|
|
16
|
+
const workspace = useWorkspaceStore()
|
|
17
|
+
const ui = useUiStore()
|
|
18
|
+
|
|
19
|
+
// Resolve whether the document-source / task-source / GitHub integrations are
|
|
20
|
+
// enabled on the backend, so each section is hidden entirely when it is off
|
|
21
|
+
// (mirrors how auth gates its UI). A 503 from a probe flips its `available` to
|
|
22
|
+
// false. Re-probe whenever the active board changes — connections are per board.
|
|
23
|
+
watch(
|
|
24
|
+
() => workspace.workspaceId,
|
|
25
|
+
(id) => {
|
|
26
|
+
if (!id) return
|
|
27
|
+
void documents.probe()
|
|
28
|
+
void tasks.probe()
|
|
29
|
+
void github.probe()
|
|
30
|
+
void slack.probe()
|
|
31
|
+
void library.probe()
|
|
32
|
+
},
|
|
33
|
+
{ immediate: true },
|
|
34
|
+
)
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<template>
|
|
38
|
+
<aside
|
|
39
|
+
class="flex h-full w-64 shrink-0 flex-col gap-4 overflow-y-auto border-r border-slate-800 bg-slate-900/80 p-3 backdrop-blur"
|
|
40
|
+
>
|
|
41
|
+
<BoardSwitcher />
|
|
42
|
+
|
|
43
|
+
<!-- Command bar launcher (⌘K) — the primary way to create blocks / pipelines
|
|
44
|
+
and reach every action below. -->
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
class="flex items-center gap-2 rounded-lg border border-slate-700 bg-slate-800/60 px-2.5 py-2 text-left text-sm text-slate-400 transition hover:border-slate-500 hover:bg-slate-800"
|
|
48
|
+
@click="ui.openCommandBar()"
|
|
49
|
+
>
|
|
50
|
+
<UIcon name="i-lucide-search" class="h-4 w-4 shrink-0" />
|
|
51
|
+
<span class="flex-1 truncate">Search or run a command…</span>
|
|
52
|
+
<UKbd value="⌘K" />
|
|
53
|
+
</button>
|
|
54
|
+
|
|
55
|
+
<section>
|
|
56
|
+
<h2 class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
57
|
+
Create
|
|
58
|
+
</h2>
|
|
59
|
+
<div class="space-y-1.5">
|
|
60
|
+
<UButton
|
|
61
|
+
block
|
|
62
|
+
color="primary"
|
|
63
|
+
variant="soft"
|
|
64
|
+
size="sm"
|
|
65
|
+
icon="i-lucide-workflow"
|
|
66
|
+
class="justify-start"
|
|
67
|
+
@click="ui.openBuilder()"
|
|
68
|
+
>
|
|
69
|
+
Build a pipeline
|
|
70
|
+
</UButton>
|
|
71
|
+
<UButton
|
|
72
|
+
block
|
|
73
|
+
color="primary"
|
|
74
|
+
variant="soft"
|
|
75
|
+
size="sm"
|
|
76
|
+
icon="i-lucide-plus"
|
|
77
|
+
class="justify-start"
|
|
78
|
+
@click="ui.openCommandBar()"
|
|
79
|
+
>
|
|
80
|
+
Add a block
|
|
81
|
+
</UButton>
|
|
82
|
+
</div>
|
|
83
|
+
</section>
|
|
84
|
+
|
|
85
|
+
<USeparator />
|
|
86
|
+
|
|
87
|
+
<section>
|
|
88
|
+
<h2 class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
89
|
+
Repositories
|
|
90
|
+
</h2>
|
|
91
|
+
<div class="space-y-1.5">
|
|
92
|
+
<UButton
|
|
93
|
+
v-if="github.available"
|
|
94
|
+
block
|
|
95
|
+
color="primary"
|
|
96
|
+
variant="soft"
|
|
97
|
+
size="sm"
|
|
98
|
+
icon="i-lucide-folder-git-2"
|
|
99
|
+
class="justify-start"
|
|
100
|
+
@click="ui.openAddService()"
|
|
101
|
+
>
|
|
102
|
+
Add from existing repo
|
|
103
|
+
</UButton>
|
|
104
|
+
<UButton
|
|
105
|
+
block
|
|
106
|
+
color="primary"
|
|
107
|
+
variant="soft"
|
|
108
|
+
size="sm"
|
|
109
|
+
icon="i-lucide-git-branch-plus"
|
|
110
|
+
class="justify-start"
|
|
111
|
+
@click="ui.openBootstrap()"
|
|
112
|
+
>
|
|
113
|
+
Bootstrap repo
|
|
114
|
+
</UButton>
|
|
115
|
+
</div>
|
|
116
|
+
</section>
|
|
117
|
+
|
|
118
|
+
<USeparator />
|
|
119
|
+
|
|
120
|
+
<section>
|
|
121
|
+
<h2 class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
122
|
+
Integrations
|
|
123
|
+
</h2>
|
|
124
|
+
<div class="space-y-1.5">
|
|
125
|
+
<UButton
|
|
126
|
+
v-if="github.available"
|
|
127
|
+
block
|
|
128
|
+
color="primary"
|
|
129
|
+
variant="soft"
|
|
130
|
+
size="sm"
|
|
131
|
+
icon="i-lucide-github"
|
|
132
|
+
class="justify-start"
|
|
133
|
+
@click="ui.openGitHub()"
|
|
134
|
+
>
|
|
135
|
+
<span class="truncate">
|
|
136
|
+
{{ github.connected ? github.connection?.accountLogin : 'Connect GitHub' }}
|
|
137
|
+
</span>
|
|
138
|
+
</UButton>
|
|
139
|
+
|
|
140
|
+
<UButton
|
|
141
|
+
v-if="slack.available"
|
|
142
|
+
block
|
|
143
|
+
color="primary"
|
|
144
|
+
variant="soft"
|
|
145
|
+
size="sm"
|
|
146
|
+
icon="i-lucide-slack"
|
|
147
|
+
class="justify-start"
|
|
148
|
+
@click="ui.openSlack()"
|
|
149
|
+
>
|
|
150
|
+
<span class="truncate">
|
|
151
|
+
{{ slack.connected ? slack.connection?.teamName : 'Connect Slack' }}
|
|
152
|
+
</span>
|
|
153
|
+
</UButton>
|
|
154
|
+
|
|
155
|
+
<template v-if="documents.available">
|
|
156
|
+
<UButton
|
|
157
|
+
v-for="src in documents.sources"
|
|
158
|
+
:key="src.source"
|
|
159
|
+
block
|
|
160
|
+
color="neutral"
|
|
161
|
+
variant="soft"
|
|
162
|
+
size="sm"
|
|
163
|
+
:icon="src.icon"
|
|
164
|
+
class="justify-start"
|
|
165
|
+
@click="ui.openDocumentConnect(src.source)"
|
|
166
|
+
>
|
|
167
|
+
<span class="truncate">
|
|
168
|
+
{{ documents.isConnected(src.source) ? src.label : `Connect ${src.label}` }}
|
|
169
|
+
</span>
|
|
170
|
+
</UButton>
|
|
171
|
+
<UButton
|
|
172
|
+
v-if="documents.anyConnected"
|
|
173
|
+
block
|
|
174
|
+
color="neutral"
|
|
175
|
+
variant="soft"
|
|
176
|
+
size="sm"
|
|
177
|
+
icon="i-lucide-file-down"
|
|
178
|
+
class="justify-start"
|
|
179
|
+
@click="ui.openDocumentImport(null)"
|
|
180
|
+
>
|
|
181
|
+
Import & spawn
|
|
182
|
+
</UButton>
|
|
183
|
+
</template>
|
|
184
|
+
|
|
185
|
+
<template v-if="tasks.available">
|
|
186
|
+
<UButton
|
|
187
|
+
v-for="src in tasks.sources"
|
|
188
|
+
:key="src.source"
|
|
189
|
+
block
|
|
190
|
+
color="neutral"
|
|
191
|
+
variant="soft"
|
|
192
|
+
size="sm"
|
|
193
|
+
:icon="src.icon"
|
|
194
|
+
class="justify-start"
|
|
195
|
+
@click="ui.openTaskConnect(src.source)"
|
|
196
|
+
>
|
|
197
|
+
<span class="truncate">
|
|
198
|
+
{{ tasks.isConnected(src.source) ? src.label : `Connect ${src.label}` }}
|
|
199
|
+
</span>
|
|
200
|
+
</UButton>
|
|
201
|
+
<UButton
|
|
202
|
+
v-if="tasks.anyConnected"
|
|
203
|
+
block
|
|
204
|
+
color="neutral"
|
|
205
|
+
variant="soft"
|
|
206
|
+
size="sm"
|
|
207
|
+
icon="i-lucide-file-down"
|
|
208
|
+
class="justify-start"
|
|
209
|
+
@click="ui.openTaskImport(null)"
|
|
210
|
+
>
|
|
211
|
+
Import issues
|
|
212
|
+
</UButton>
|
|
213
|
+
</template>
|
|
214
|
+
</div>
|
|
215
|
+
</section>
|
|
216
|
+
|
|
217
|
+
<template v-if="library.available">
|
|
218
|
+
<USeparator />
|
|
219
|
+
<section>
|
|
220
|
+
<h2 class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
221
|
+
Workspace context
|
|
222
|
+
</h2>
|
|
223
|
+
<UButton
|
|
224
|
+
block
|
|
225
|
+
color="primary"
|
|
226
|
+
variant="soft"
|
|
227
|
+
size="sm"
|
|
228
|
+
icon="i-lucide-book-marked"
|
|
229
|
+
class="justify-start"
|
|
230
|
+
@click="ui.openFragmentLibrary()"
|
|
231
|
+
>
|
|
232
|
+
Context fragments
|
|
233
|
+
</UButton>
|
|
234
|
+
</section>
|
|
235
|
+
</template>
|
|
236
|
+
|
|
237
|
+
<USeparator />
|
|
238
|
+
<section>
|
|
239
|
+
<h2 class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
240
|
+
Configuration
|
|
241
|
+
</h2>
|
|
242
|
+
<div class="space-y-1.5">
|
|
243
|
+
<UButton
|
|
244
|
+
block
|
|
245
|
+
color="primary"
|
|
246
|
+
variant="soft"
|
|
247
|
+
size="sm"
|
|
248
|
+
icon="i-lucide-git-merge"
|
|
249
|
+
class="justify-start"
|
|
250
|
+
@click="ui.openMergeThresholds()"
|
|
251
|
+
>
|
|
252
|
+
Merge thresholds
|
|
253
|
+
</UButton>
|
|
254
|
+
<UButton
|
|
255
|
+
block
|
|
256
|
+
color="primary"
|
|
257
|
+
variant="soft"
|
|
258
|
+
size="sm"
|
|
259
|
+
icon="i-lucide-sliders-horizontal"
|
|
260
|
+
class="justify-start"
|
|
261
|
+
@click="ui.openWorkspaceSettings()"
|
|
262
|
+
>
|
|
263
|
+
Workspace settings
|
|
264
|
+
</UButton>
|
|
265
|
+
<UButton
|
|
266
|
+
block
|
|
267
|
+
color="primary"
|
|
268
|
+
variant="soft"
|
|
269
|
+
size="sm"
|
|
270
|
+
icon="i-lucide-activity"
|
|
271
|
+
class="justify-start"
|
|
272
|
+
@click="ui.openDatadog()"
|
|
273
|
+
>
|
|
274
|
+
Post-release health
|
|
275
|
+
</UButton>
|
|
276
|
+
<UButton
|
|
277
|
+
block
|
|
278
|
+
color="primary"
|
|
279
|
+
variant="soft"
|
|
280
|
+
size="sm"
|
|
281
|
+
icon="i-lucide-cpu"
|
|
282
|
+
class="justify-start"
|
|
283
|
+
@click="ui.openModelDefaults()"
|
|
284
|
+
>
|
|
285
|
+
Default models
|
|
286
|
+
</UButton>
|
|
287
|
+
<UButton
|
|
288
|
+
block
|
|
289
|
+
color="primary"
|
|
290
|
+
variant="soft"
|
|
291
|
+
size="sm"
|
|
292
|
+
icon="i-lucide-book-open-check"
|
|
293
|
+
class="justify-start"
|
|
294
|
+
@click="ui.openServiceFragmentDefaults()"
|
|
295
|
+
>
|
|
296
|
+
Default service best practices
|
|
297
|
+
</UButton>
|
|
298
|
+
<UButton
|
|
299
|
+
block
|
|
300
|
+
color="primary"
|
|
301
|
+
variant="soft"
|
|
302
|
+
size="sm"
|
|
303
|
+
icon="i-lucide-server"
|
|
304
|
+
class="justify-start"
|
|
305
|
+
@click="ui.openLocalModels()"
|
|
306
|
+
>
|
|
307
|
+
My local runners
|
|
308
|
+
</UButton>
|
|
309
|
+
</div>
|
|
310
|
+
</section>
|
|
311
|
+
|
|
312
|
+
<UserMenu class="mt-auto" />
|
|
313
|
+
</aside>
|
|
314
|
+
</template>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
const workspace = useWorkspaceStore()
|
|
5
|
+
|
|
6
|
+
const spend = computed(() => workspace.spend)
|
|
7
|
+
/** Show the large warning only once the budget has been reached. */
|
|
8
|
+
const exceeded = computed(() => spend.value?.exceeded ?? false)
|
|
9
|
+
|
|
10
|
+
function money(amount: number, currency: string) {
|
|
11
|
+
try {
|
|
12
|
+
return new Intl.NumberFormat(undefined, { style: 'currency', currency }).format(amount)
|
|
13
|
+
} catch {
|
|
14
|
+
// Fall back if the currency code isn't recognised by the runtime.
|
|
15
|
+
return `${amount.toFixed(2)} ${currency}`
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const tokens = computed(() => {
|
|
20
|
+
const s = spend.value
|
|
21
|
+
if (!s) return ''
|
|
22
|
+
return new Intl.NumberFormat().format(s.inputTokens + s.outputTokens)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const resuming = ref(false)
|
|
26
|
+
async function resume() {
|
|
27
|
+
resuming.value = true
|
|
28
|
+
try {
|
|
29
|
+
await workspace.resumeSpend()
|
|
30
|
+
} finally {
|
|
31
|
+
resuming.value = false
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<template>
|
|
37
|
+
<Transition name="fade">
|
|
38
|
+
<div
|
|
39
|
+
v-if="exceeded && spend"
|
|
40
|
+
class="absolute inset-x-0 top-0 z-50 flex justify-center px-4 pt-4"
|
|
41
|
+
>
|
|
42
|
+
<div
|
|
43
|
+
class="w-full max-w-3xl rounded-2xl border-2 border-red-500/70 bg-red-950/95 p-5 shadow-2xl backdrop-blur"
|
|
44
|
+
role="alert"
|
|
45
|
+
>
|
|
46
|
+
<div class="flex items-start gap-4">
|
|
47
|
+
<UIcon name="i-lucide-octagon-alert" class="mt-0.5 h-10 w-10 shrink-0 text-red-400" />
|
|
48
|
+
<div class="min-w-0 flex-1">
|
|
49
|
+
<h2 class="text-lg font-semibold text-red-100">
|
|
50
|
+
Spend limit reached — agent execution paused
|
|
51
|
+
</h2>
|
|
52
|
+
<p class="mt-1 text-sm text-red-200/90">
|
|
53
|
+
This month's token spend has hit the configured budget, so running pipelines were
|
|
54
|
+
paused to avoid further cost. Execution resumes automatically when the budget rolls
|
|
55
|
+
over next month, or once the limit is raised.
|
|
56
|
+
</p>
|
|
57
|
+
|
|
58
|
+
<dl class="mt-4 grid grid-cols-2 gap-3 sm:grid-cols-3">
|
|
59
|
+
<div class="rounded-lg bg-red-900/50 px-3 py-2">
|
|
60
|
+
<dt class="text-[11px] uppercase tracking-wide text-red-300/80">Spent</dt>
|
|
61
|
+
<dd class="text-base font-semibold tabular-nums text-red-50">
|
|
62
|
+
{{ money(spend.costSpent, spend.currency) }}
|
|
63
|
+
</dd>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="rounded-lg bg-red-900/50 px-3 py-2">
|
|
66
|
+
<dt class="text-[11px] uppercase tracking-wide text-red-300/80">Budget</dt>
|
|
67
|
+
<dd class="text-base font-semibold tabular-nums text-red-50">
|
|
68
|
+
{{ money(spend.costLimit, spend.currency) }}
|
|
69
|
+
</dd>
|
|
70
|
+
</div>
|
|
71
|
+
<div class="rounded-lg bg-red-900/50 px-3 py-2">
|
|
72
|
+
<dt class="text-[11px] uppercase tracking-wide text-red-300/80">Tokens</dt>
|
|
73
|
+
<dd class="text-base font-semibold tabular-nums text-red-50">{{ tokens }}</dd>
|
|
74
|
+
</div>
|
|
75
|
+
</dl>
|
|
76
|
+
|
|
77
|
+
<div class="mt-4 flex items-center gap-3">
|
|
78
|
+
<UButton
|
|
79
|
+
color="error"
|
|
80
|
+
variant="solid"
|
|
81
|
+
icon="i-lucide-play"
|
|
82
|
+
:loading="resuming"
|
|
83
|
+
@click="resume"
|
|
84
|
+
>
|
|
85
|
+
Resume anyway
|
|
86
|
+
</UButton>
|
|
87
|
+
<span class="text-xs text-red-300/70">
|
|
88
|
+
Resuming continues spending; it will pause again if still over budget.
|
|
89
|
+
</span>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</Transition>
|
|
96
|
+
</template>
|
|
97
|
+
|
|
98
|
+
<style scoped>
|
|
99
|
+
.fade-enter-active,
|
|
100
|
+
.fade-leave-active {
|
|
101
|
+
transition: opacity 0.2s ease;
|
|
102
|
+
}
|
|
103
|
+
.fade-enter-from,
|
|
104
|
+
.fade-leave-to {
|
|
105
|
+
opacity: 0;
|
|
106
|
+
}
|
|
107
|
+
</style>
|