@cat-factory/app 0.46.9 → 0.46.11
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/board/AddTaskModal.vue +106 -88
- package/app/components/board/AgentFailureCard.vue +8 -5
- package/app/components/board/AgentStopButton.vue +8 -5
- package/app/components/board/BoardCanvas.vue +13 -9
- package/app/components/board/RecurringPipelineModal.vue +33 -23
- package/app/components/board/nodes/BlockNode.vue +50 -34
- package/app/components/board/nodes/DecisionBadge.vue +6 -3
- package/app/components/board/nodes/DraggableTask.vue +2 -1
- package/app/components/board/nodes/EpicNode.vue +10 -3
- package/app/components/board/nodes/ModuleFrame.vue +6 -5
- package/app/components/board/nodes/TaskCard.vue +40 -22
- package/app/components/board/nodes/TaskPipelineMini.vue +4 -3
- package/app/components/layout/BoardToolbar.vue +26 -19
- package/app/components/observability/StepMetricsBar.vue +31 -11
- package/app/components/observability/StepModelActivity.vue +3 -2
- package/app/components/panels/AgentStepDetail.vue +52 -39
- package/app/components/panels/DecisionModal.vue +12 -5
- package/app/components/panels/GenericStructuredResultView.vue +16 -6
- package/app/components/panels/InspectorPanel.vue +35 -28
- package/app/components/panels/ObservabilityPanel.vue +92 -49
- package/app/components/panels/StepMetadataCard.vue +87 -30
- package/app/components/panels/StepRestartControl.vue +4 -3
- package/app/components/panels/StepRunMeta.vue +23 -10
- package/app/components/panels/StepTestReport.vue +14 -11
- package/app/components/panels/inspector/ContainerSummary.vue +25 -14
- package/app/components/panels/inspector/EpicChildren.vue +7 -4
- package/app/components/panels/inspector/RecurringScheduleSettings.vue +60 -19
- package/app/components/panels/inspector/ServiceFragments.vue +3 -2
- package/app/components/panels/inspector/ServiceReleaseHealthConfig.vue +18 -13
- package/app/components/panels/inspector/ServiceTestConfig.vue +50 -39
- package/app/components/panels/inspector/TaskAgentConfig.vue +6 -5
- package/app/components/panels/inspector/TaskDependencies.vue +9 -4
- package/app/components/panels/inspector/TaskEstimateBadge.vue +14 -13
- package/app/components/panels/inspector/TaskExecution.vue +65 -31
- package/app/components/panels/inspector/TaskRunSettings.vue +93 -56
- package/app/components/panels/inspector/TaskStructure.vue +5 -4
- package/i18n/locales/en.json +606 -0
- package/i18n/locales/es.json +596 -2
- package/i18n/locales/fr.json +596 -2
- package/i18n/locales/pl.json +596 -2
- package/i18n/locales/uk.json +596 -2
- package/package.json +1 -1
|
@@ -13,6 +13,7 @@ const ui = useUiStore()
|
|
|
13
13
|
const agentRuns = useAgentRunsStore()
|
|
14
14
|
const reviews = useReviewStage()
|
|
15
15
|
const toast = useToast()
|
|
16
|
+
const { t } = useI18n()
|
|
16
17
|
|
|
17
18
|
const task = computed<Block | undefined>(() => board.getBlock(props.taskId))
|
|
18
19
|
const statusMeta = computed(() => (task.value ? STATUS_META[task.value.status] : null))
|
|
@@ -43,7 +44,9 @@ const defaultPipeline = computed(
|
|
|
43
44
|
|
|
44
45
|
/** The PR the implementer agent opened for this task, if any. */
|
|
45
46
|
const pr = computed(() => task.value?.pullRequest)
|
|
46
|
-
const prLabel = computed(() =>
|
|
47
|
+
const prLabel = computed(() =>
|
|
48
|
+
pr.value?.number ? t('board.task.prNumber', { number: pr.value.number }) : t('board.task.pr'),
|
|
49
|
+
)
|
|
47
50
|
|
|
48
51
|
// This task's current agent run (if any). A failed run must surface the shared
|
|
49
52
|
// failure banner + retry — NOT a stuck progress bar — so the card never looks
|
|
@@ -65,15 +68,18 @@ const starting = ref(false)
|
|
|
65
68
|
async function run() {
|
|
66
69
|
if (!runnable.value) {
|
|
67
70
|
toast.add({
|
|
68
|
-
title: '
|
|
69
|
-
description:
|
|
71
|
+
title: t('board.task.blockedByDependenciesTitle'),
|
|
72
|
+
description: t('board.task.waitingOn', { deps: unmet.value.map((d) => d.title).join(', ') }),
|
|
70
73
|
icon: 'i-lucide-lock',
|
|
71
74
|
})
|
|
72
75
|
return
|
|
73
76
|
}
|
|
74
77
|
const pipeline = defaultPipeline.value
|
|
75
78
|
if (!pipeline) {
|
|
76
|
-
toast.add({
|
|
79
|
+
toast.add({
|
|
80
|
+
title: t('board.task.noPipelineTitle'),
|
|
81
|
+
description: t('board.task.noPipelineBody'),
|
|
82
|
+
})
|
|
77
83
|
return
|
|
78
84
|
}
|
|
79
85
|
starting.value = true
|
|
@@ -108,11 +114,11 @@ const pendingDecision = computed(() =>
|
|
|
108
114
|
const reviewStage = computed(() => reviews.stageForBlock(props.taskId))
|
|
109
115
|
const reviewStageLabel = computed(() =>
|
|
110
116
|
reviewStage.value === 'incorporating'
|
|
111
|
-
? '
|
|
117
|
+
? t('board.task.incorporatingAnswers')
|
|
112
118
|
: reviewStage.value === 'reviewing'
|
|
113
|
-
? '
|
|
119
|
+
? t('board.task.reReviewing')
|
|
114
120
|
: reviewStage.value === 'recommending'
|
|
115
|
-
? '
|
|
121
|
+
? t('board.task.recommending')
|
|
116
122
|
: null,
|
|
117
123
|
)
|
|
118
124
|
const pendingApproval = computed(() => {
|
|
@@ -135,17 +141,17 @@ const attention = computed<{
|
|
|
135
141
|
const d = pendingDecision.value
|
|
136
142
|
if (d)
|
|
137
143
|
return {
|
|
138
|
-
label: '
|
|
144
|
+
label: t('board.task.decisionNeeded'),
|
|
139
145
|
icon: 'i-lucide-circle-help',
|
|
140
|
-
action: '
|
|
146
|
+
action: t('board.task.resolve'),
|
|
141
147
|
open: () => ui.openDecision(d.instanceId, d.decision.id),
|
|
142
148
|
}
|
|
143
149
|
const a = pendingApproval.value
|
|
144
150
|
if (a)
|
|
145
151
|
return {
|
|
146
|
-
label: '
|
|
152
|
+
label: t('board.task.approvalNeeded'),
|
|
147
153
|
icon: 'i-lucide-shield-check',
|
|
148
|
-
action: '
|
|
154
|
+
action: t('board.task.approve'),
|
|
149
155
|
open: () => ui.openApprovalDetail(a.instanceId, a.approval.id),
|
|
150
156
|
}
|
|
151
157
|
return null
|
|
@@ -155,7 +161,7 @@ const attention = computed<{
|
|
|
155
161
|
* decision/approval reason, otherwise the generic status label. */
|
|
156
162
|
const statusText = computed(() =>
|
|
157
163
|
runFailed.value
|
|
158
|
-
? '
|
|
164
|
+
? t('board.task.failed')
|
|
159
165
|
: (reviewStageLabel.value ?? attention.value?.label ?? statusMeta.value?.label ?? ''),
|
|
160
166
|
)
|
|
161
167
|
|
|
@@ -191,7 +197,11 @@ function selectTask() {
|
|
|
191
197
|
v-if="schedule"
|
|
192
198
|
name="i-lucide-repeat"
|
|
193
199
|
class="h-3 w-3 shrink-0 text-indigo-400"
|
|
194
|
-
:title="
|
|
200
|
+
:title="
|
|
201
|
+
schedule.enabled
|
|
202
|
+
? t('board.task.recurringPipeline')
|
|
203
|
+
: t('board.task.recurringPipelinePaused')
|
|
204
|
+
"
|
|
195
205
|
/>
|
|
196
206
|
<span
|
|
197
207
|
class="ml-auto truncate text-[9px] uppercase tracking-wide"
|
|
@@ -211,7 +221,7 @@ function selectTask() {
|
|
|
211
221
|
<button
|
|
212
222
|
type="button"
|
|
213
223
|
class="nodrag shrink-0 cursor-crosshair touch-none rounded-full p-0.5 text-slate-500 hover:bg-slate-800 hover:text-amber-400 pointer-coarse:p-2.5"
|
|
214
|
-
title="
|
|
224
|
+
:title="t('board.task.dragToConnect')"
|
|
215
225
|
@pointerdown.stop="startConnect(task.id, $event)"
|
|
216
226
|
@click.stop
|
|
217
227
|
>
|
|
@@ -302,12 +312,20 @@ function selectTask() {
|
|
|
302
312
|
:disabled="!runnable || starting"
|
|
303
313
|
:title="
|
|
304
314
|
runnable
|
|
305
|
-
?
|
|
306
|
-
|
|
315
|
+
? t('board.task.startPipeline', {
|
|
316
|
+
name: defaultPipeline?.name ?? t('board.task.pipelineFallback'),
|
|
317
|
+
})
|
|
318
|
+
: t('board.task.waitingOn', { deps: unmet.map((d) => d.title).join(', ') })
|
|
307
319
|
"
|
|
308
320
|
@click.stop="run"
|
|
309
321
|
>
|
|
310
|
-
{{
|
|
322
|
+
{{
|
|
323
|
+
starting
|
|
324
|
+
? t('board.task.starting')
|
|
325
|
+
: runnable
|
|
326
|
+
? t('board.task.start')
|
|
327
|
+
: t('board.task.blocked')
|
|
328
|
+
}}
|
|
311
329
|
</UButton>
|
|
312
330
|
<span
|
|
313
331
|
v-if="runnable && defaultPipeline"
|
|
@@ -328,7 +346,7 @@ function selectTask() {
|
|
|
328
346
|
variant="soft"
|
|
329
347
|
size="xs"
|
|
330
348
|
icon="i-lucide-git-pull-request"
|
|
331
|
-
:title="
|
|
349
|
+
:title="t('board.task.openPrOnGithub', { pr: prLabel })"
|
|
332
350
|
@click.stop
|
|
333
351
|
>
|
|
334
352
|
{{ prLabel }}
|
|
@@ -340,7 +358,7 @@ function selectTask() {
|
|
|
340
358
|
icon="i-lucide-scan-eye"
|
|
341
359
|
@click.stop="review"
|
|
342
360
|
>
|
|
343
|
-
|
|
361
|
+
{{ t('board.task.review') }}
|
|
344
362
|
</UButton>
|
|
345
363
|
<UButton
|
|
346
364
|
color="success"
|
|
@@ -349,7 +367,7 @@ function selectTask() {
|
|
|
349
367
|
icon="i-lucide-git-merge"
|
|
350
368
|
@click.stop="merge"
|
|
351
369
|
>
|
|
352
|
-
|
|
370
|
+
{{ t('board.task.merge') }}
|
|
353
371
|
</UButton>
|
|
354
372
|
</template>
|
|
355
373
|
|
|
@@ -357,7 +375,7 @@ function selectTask() {
|
|
|
357
375
|
v-else-if="task.status === 'done'"
|
|
358
376
|
class="inline-flex items-center gap-1 text-[9px] text-emerald-400"
|
|
359
377
|
>
|
|
360
|
-
<UIcon name="i-lucide-check-check" class="h-3 w-3" /> implemented
|
|
378
|
+
<UIcon name="i-lucide-check-check" class="h-3 w-3" /> {{ t('board.task.implemented') }}
|
|
361
379
|
</span>
|
|
362
380
|
</div>
|
|
363
381
|
|
|
@@ -368,7 +386,7 @@ function selectTask() {
|
|
|
368
386
|
>
|
|
369
387
|
<span
|
|
370
388
|
class="inline-flex items-center gap-1 rounded bg-violet-500/15 px-1.5 py-0.5 text-[9px] text-violet-200"
|
|
371
|
-
:title="
|
|
389
|
+
:title="t('board.task.module', { name: task.moduleName })"
|
|
372
390
|
>
|
|
373
391
|
<UIcon :name="MODULE_META.icon" class="h-3 w-3" :style="{ color: MODULE_META.color }" />
|
|
374
392
|
{{ task.moduleName }}
|
|
@@ -21,6 +21,7 @@ const execution = useExecutionStore()
|
|
|
21
21
|
const ui = useUiStore()
|
|
22
22
|
const expansion = useTaskExpansionStore()
|
|
23
23
|
const reviews = useReviewStage()
|
|
24
|
+
const { t } = useI18n()
|
|
24
25
|
const { lod } = useSemanticZoom()
|
|
25
26
|
|
|
26
27
|
const instance = computed(() => execution.getByBlock(props.taskId))
|
|
@@ -75,12 +76,12 @@ const ITEM_ICON: Record<string, string> = {
|
|
|
75
76
|
<div v-if="showSteps" class="mt-2 space-y-1 border-t border-slate-800 pt-2">
|
|
76
77
|
<div class="flex items-center gap-1 text-[9px] uppercase tracking-wide text-slate-500">
|
|
77
78
|
<UIcon name="i-lucide-workflow" class="h-2.5 w-2.5" />
|
|
78
|
-
|
|
79
|
+
{{ t('board.task.buildSteps') }}
|
|
79
80
|
</div>
|
|
80
81
|
<div v-for="(s, i) in steps" :key="i" class="rounded bg-slate-900/60 px-1.5 py-1">
|
|
81
82
|
<div
|
|
82
83
|
class="flex cursor-pointer items-center gap-1"
|
|
83
|
-
:title="`${agentKindMeta(s.agentKind).label} — ${agentKindMeta(s.agentKind).description}\
|
|
84
|
+
:title="`${agentKindMeta(s.agentKind).label} — ${agentKindMeta(s.agentKind).description}\n${t('board.task.clickToViewStep')}`"
|
|
84
85
|
@click.stop="openStep(i)"
|
|
85
86
|
>
|
|
86
87
|
<UIcon
|
|
@@ -131,7 +132,7 @@ const ITEM_ICON: Record<string, string> = {
|
|
|
131
132
|
@click.stop="ui.openApprovalDetail(instance.id, s.approval.id)"
|
|
132
133
|
>
|
|
133
134
|
<UIcon name="i-lucide-shield-check" class="h-2.5 w-2.5" />
|
|
134
|
-
|
|
135
|
+
{{ t('board.task.reviewAndApprove') }}
|
|
135
136
|
</button>
|
|
136
137
|
|
|
137
138
|
<!-- per-step subtask progress bar -->
|
|
@@ -8,15 +8,20 @@ const execution = useExecutionStore()
|
|
|
8
8
|
const workspace = useWorkspaceStore()
|
|
9
9
|
const services = useServicesStore()
|
|
10
10
|
const toast = useToast()
|
|
11
|
+
const { t, n } = useI18n()
|
|
11
12
|
const { fitView, zoomIn, zoomOut } = useBoardFlow()
|
|
12
13
|
|
|
13
14
|
async function mountService(serviceId: string, title: string) {
|
|
14
15
|
try {
|
|
15
16
|
await services.mount(serviceId)
|
|
16
|
-
toast.add({
|
|
17
|
+
toast.add({
|
|
18
|
+
title: t('board.toolbar.serviceAdded', { title }),
|
|
19
|
+
icon: 'i-lucide-box',
|
|
20
|
+
color: 'success',
|
|
21
|
+
})
|
|
17
22
|
} catch (e) {
|
|
18
23
|
toast.add({
|
|
19
|
-
title: '
|
|
24
|
+
title: t('board.toolbar.serviceAddFailed'),
|
|
20
25
|
description: e instanceof Error ? e.message : String(e),
|
|
21
26
|
color: 'error',
|
|
22
27
|
})
|
|
@@ -38,16 +43,16 @@ const mountableItems = computed(() =>
|
|
|
38
43
|
)
|
|
39
44
|
|
|
40
45
|
const zoomPct = computed(() => Math.round(ui.zoom * 100))
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
)
|
|
46
|
+
// Exhaustive (tier-2) map from level-of-detail → its label key, so adding an LOD
|
|
47
|
+
// without a label fails the typecheck rather than leaking a raw key.
|
|
48
|
+
const LOD_LABEL_KEYS = {
|
|
49
|
+
far: 'board.toolbar.lod.far',
|
|
50
|
+
mid: 'board.toolbar.lod.mid',
|
|
51
|
+
close: 'board.toolbar.lod.close',
|
|
52
|
+
steps: 'board.toolbar.lod.steps',
|
|
53
|
+
subtasks: 'board.toolbar.lod.subtasks',
|
|
54
|
+
} as const
|
|
55
|
+
const lodLabel = computed(() => t(LOD_LABEL_KEYS[ui.lod]))
|
|
51
56
|
|
|
52
57
|
// Live spend indicator: shown once any tokens have been metered this period.
|
|
53
58
|
const spend = computed(() => workspace.spend)
|
|
@@ -55,11 +60,11 @@ const showSpend = computed(() => !!spend.value && spend.value.costSpent > 0)
|
|
|
55
60
|
const spendLabel = computed(() => {
|
|
56
61
|
const s = spend.value
|
|
57
62
|
if (!s) return ''
|
|
58
|
-
const fmt = (
|
|
63
|
+
const fmt = (value: number) => {
|
|
59
64
|
try {
|
|
60
|
-
return
|
|
65
|
+
return n(value, { key: 'currency', currency: s.currency })
|
|
61
66
|
} catch {
|
|
62
|
-
return `${
|
|
67
|
+
return `${value.toFixed(2)} ${s.currency}`
|
|
63
68
|
}
|
|
64
69
|
}
|
|
65
70
|
return `${fmt(s.costSpent)} / ${fmt(s.costLimit)}`
|
|
@@ -70,7 +75,7 @@ const decisionItems = computed(() =>
|
|
|
70
75
|
execution.openDecisions.map((d) => {
|
|
71
76
|
const b = board.getBlock(d.blockId)
|
|
72
77
|
return {
|
|
73
|
-
label: b?.title ?? '
|
|
78
|
+
label: b?.title ?? t('common.block'),
|
|
74
79
|
description: d.decision.question,
|
|
75
80
|
icon: 'i-lucide-circle-help',
|
|
76
81
|
onSelect: () => ui.openDecision(d.instanceId, d.decision.id),
|
|
@@ -127,7 +132,7 @@ const decisionItems = computed(() =>
|
|
|
127
132
|
>
|
|
128
133
|
{{ execution.pendingDecisionCount
|
|
129
134
|
}}<span class="hidden sm:inline"
|
|
130
|
-
> {{
|
|
135
|
+
> {{ t('board.toolbar.decisionWord', execution.pendingDecisionCount) }}</span
|
|
131
136
|
>
|
|
132
137
|
</UButton>
|
|
133
138
|
</UDropdownMenu>
|
|
@@ -135,7 +140,7 @@ const decisionItems = computed(() =>
|
|
|
135
140
|
<!-- in-org sharing: add an existing org service to this board -->
|
|
136
141
|
<UDropdownMenu v-if="mountableItems.length" :items="mountableItems">
|
|
137
142
|
<UButton color="neutral" variant="ghost" size="sm" icon="i-lucide-plus-circle">
|
|
138
|
-
<span class="hidden sm:inline">{{
|
|
143
|
+
<span class="hidden sm:inline">{{ t('board.toolbar.addService') }}</span>
|
|
139
144
|
</UButton>
|
|
140
145
|
</UDropdownMenu>
|
|
141
146
|
|
|
@@ -149,7 +154,9 @@ const decisionItems = computed(() =>
|
|
|
149
154
|
variant="soft"
|
|
150
155
|
size="sm"
|
|
151
156
|
icon="i-lucide-wallet"
|
|
152
|
-
:title="
|
|
157
|
+
:title="
|
|
158
|
+
spend?.exceeded ? t('board.toolbar.spendLimitReached') : t('board.toolbar.spendTitle')
|
|
159
|
+
"
|
|
153
160
|
>
|
|
154
161
|
<span class="hidden sm:inline">{{ spendLabel }}</span>
|
|
155
162
|
</UButton>
|
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
const props = defineProps<{ metrics: StepMetrics; clickable?: boolean }>()
|
|
20
20
|
defineEmits<{ inspect: [] }>()
|
|
21
21
|
|
|
22
|
+
const { t } = useI18n()
|
|
23
|
+
|
|
22
24
|
const m = computed(() => props.metrics)
|
|
23
25
|
const headroom = computed(() => headroomRatio(m.value))
|
|
24
26
|
const transport = computed(() => transportRatio(m.value))
|
|
@@ -38,24 +40,33 @@ const headroomTone = computed(() => headroomColor(headroom.value, m.value.trunca
|
|
|
38
40
|
<!-- header line: call count + tokens + warning/error badges -->
|
|
39
41
|
<div class="flex items-center gap-2">
|
|
40
42
|
<UIcon name="i-lucide-activity" class="h-3.5 w-3.5 shrink-0 text-slate-500" />
|
|
41
|
-
<span class="text-slate-300">
|
|
43
|
+
<span class="text-slate-300">
|
|
44
|
+
{{ t('observability.metricsBar.calls', { count: m.calls }, m.calls) }}
|
|
45
|
+
</span>
|
|
42
46
|
<span class="text-slate-500">·</span>
|
|
43
|
-
<span
|
|
47
|
+
<span
|
|
48
|
+
class="tabular-nums text-slate-400"
|
|
49
|
+
:title="t('observability.metricsBar.promptCompletionTokens')"
|
|
50
|
+
>
|
|
44
51
|
{{ formatTokens(m.promptTokens) }}↑ {{ formatTokens(m.completionTokens) }}↓
|
|
45
52
|
</span>
|
|
46
53
|
<span
|
|
47
54
|
v-if="(m.cachedPromptTokens ?? 0) > 0"
|
|
48
55
|
class="tabular-nums text-emerald-400/80"
|
|
49
|
-
title="
|
|
56
|
+
:title="t('observability.metricsBar.cachedTokensHint')"
|
|
50
57
|
>
|
|
51
|
-
|
|
58
|
+
{{
|
|
59
|
+
t('observability.metricsBar.cachedTokens', {
|
|
60
|
+
tokens: formatTokens(m.cachedPromptTokens ?? 0),
|
|
61
|
+
})
|
|
62
|
+
}}
|
|
52
63
|
</span>
|
|
53
64
|
<div class="ml-auto flex items-center gap-1">
|
|
54
65
|
<UBadge v-if="m.errors > 0" color="error" variant="subtle" size="sm">
|
|
55
|
-
{{
|
|
66
|
+
{{ t('observability.metricsBar.errors', { count: m.errors }, m.errors) }}
|
|
56
67
|
</UBadge>
|
|
57
68
|
<UBadge v-if="m.warnings > 0" color="warning" variant="subtle" size="sm">
|
|
58
|
-
{{
|
|
69
|
+
{{ t('observability.metricsBar.warnings', { count: m.warnings }, m.warnings) }}
|
|
59
70
|
</UBadge>
|
|
60
71
|
<UIcon v-if="clickable" name="i-lucide-chevron-right" class="h-3.5 w-3.5 text-slate-600" />
|
|
61
72
|
</div>
|
|
@@ -64,7 +75,7 @@ const headroomTone = computed(() => headroomColor(headroom.value, m.value.trunca
|
|
|
64
75
|
<!-- output-limit headroom -->
|
|
65
76
|
<div v-if="headroom !== null" class="mt-2">
|
|
66
77
|
<div class="flex items-center justify-between text-[11px]">
|
|
67
|
-
<span class="text-slate-500">
|
|
78
|
+
<span class="text-slate-500">{{ t('observability.metricsBar.outputLimit') }}</span>
|
|
68
79
|
<span class="tabular-nums" :class="headroomTone">
|
|
69
80
|
{{ formatTokens(m.peakCompletionTokens) }} /
|
|
70
81
|
{{ formatTokens(m.maxOutputTokens ?? 0) }} ({{ pct(headroom) }}%)
|
|
@@ -84,14 +95,20 @@ const headroomTone = computed(() => headroomColor(headroom.value, m.value.trunca
|
|
|
84
95
|
/>
|
|
85
96
|
</div>
|
|
86
97
|
<p v-if="m.truncatedCalls > 0" class="mt-1 text-[11px] text-rose-400">
|
|
87
|
-
{{
|
|
98
|
+
{{
|
|
99
|
+
t(
|
|
100
|
+
'observability.metricsBar.truncatedCalls',
|
|
101
|
+
{ count: m.truncatedCalls },
|
|
102
|
+
m.truncatedCalls,
|
|
103
|
+
)
|
|
104
|
+
}}
|
|
88
105
|
</p>
|
|
89
106
|
</div>
|
|
90
107
|
|
|
91
108
|
<!-- transport overhead vs model execution -->
|
|
92
109
|
<div v-if="transport !== null" class="mt-2">
|
|
93
110
|
<div class="flex items-center justify-between text-[11px]">
|
|
94
|
-
<span class="text-slate-500">
|
|
111
|
+
<span class="text-slate-500">{{ t('observability.metricsBar.transportVsExecution') }}</span>
|
|
95
112
|
<span class="tabular-nums text-slate-400">
|
|
96
113
|
{{ formatMs(m.overheadMs) }} / {{ formatMs(m.upstreamMs) }}
|
|
97
114
|
</span>
|
|
@@ -100,9 +117,12 @@ const headroomTone = computed(() => headroomColor(headroom.value, m.value.trunca
|
|
|
100
117
|
<div
|
|
101
118
|
class="h-full bg-sky-400/80"
|
|
102
119
|
:style="{ width: `${pct(transport)}%` }"
|
|
103
|
-
title="
|
|
120
|
+
:title="t('observability.metricsBar.transportOverhead')"
|
|
121
|
+
/>
|
|
122
|
+
<div
|
|
123
|
+
class="h-full bg-indigo-400/80 flex-1"
|
|
124
|
+
:title="t('observability.metricsBar.modelExecution')"
|
|
104
125
|
/>
|
|
105
|
-
<div class="h-full bg-indigo-400/80 flex-1" title="Model execution" />
|
|
106
126
|
</div>
|
|
107
127
|
</div>
|
|
108
128
|
</div>
|
|
@@ -18,6 +18,7 @@ const props = defineProps<{
|
|
|
18
18
|
}>()
|
|
19
19
|
|
|
20
20
|
const ui = useUiStore()
|
|
21
|
+
const { t } = useI18n()
|
|
21
22
|
const hasCalls = computed(() => !!props.metrics && props.metrics.calls > 0)
|
|
22
23
|
|
|
23
24
|
function openObservability() {
|
|
@@ -29,14 +30,14 @@ function openObservability() {
|
|
|
29
30
|
<div v-if="instanceId || hasCalls">
|
|
30
31
|
<div class="mb-1 flex items-center justify-between">
|
|
31
32
|
<span class="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
32
|
-
|
|
33
|
+
{{ t('observability.modelActivity') }}
|
|
33
34
|
</span>
|
|
34
35
|
<button
|
|
35
36
|
v-if="instanceId"
|
|
36
37
|
class="text-[11px] text-sky-400 hover:text-sky-300"
|
|
37
38
|
@click="openObservability"
|
|
38
39
|
>
|
|
39
|
-
|
|
40
|
+
{{ t('observability.viewAllCalls') }}
|
|
40
41
|
</button>
|
|
41
42
|
</div>
|
|
42
43
|
<StepMetricsBar
|