arkaos 3.31.0 → 3.33.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/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.33.0
|
|
@@ -73,14 +73,27 @@ const {
|
|
|
73
73
|
{ query: computed(() => ({ period: period.value })) },
|
|
74
74
|
)
|
|
75
75
|
|
|
76
|
+
// PR90c v3.33.0 — let the operator pick 7d / 14d / 30d.
|
|
77
|
+
const trendDays = ref<7 | 14 | 30>(7)
|
|
78
|
+
const trendDaysOptions = [
|
|
79
|
+
{ label: '7 days', value: 7 },
|
|
80
|
+
{ label: '14 days', value: 14 },
|
|
81
|
+
{ label: '30 days', value: 30 },
|
|
82
|
+
]
|
|
76
83
|
const {
|
|
77
84
|
data: trend,
|
|
78
85
|
refresh: refreshTrend,
|
|
79
|
-
} = fetchApi<TrendResponse>(
|
|
86
|
+
} = fetchApi<TrendResponse>(
|
|
87
|
+
'/api/llm-costs/trend',
|
|
88
|
+
{ query: computed(() => ({ days: trendDays.value })) },
|
|
89
|
+
)
|
|
80
90
|
|
|
81
91
|
watch(period, async () => {
|
|
82
92
|
await refresh()
|
|
83
93
|
})
|
|
94
|
+
watch(trendDays, async () => {
|
|
95
|
+
await refreshTrend()
|
|
96
|
+
})
|
|
84
97
|
|
|
85
98
|
// ─── View tabs ───────────────────────────────────────────────────────────
|
|
86
99
|
|
|
@@ -248,12 +261,21 @@ async function refreshAll() {
|
|
|
248
261
|
</div>
|
|
249
262
|
</UCard>
|
|
250
263
|
|
|
251
|
-
<!--
|
|
264
|
+
<!-- PR90c v3.33.0 — trend with selectable window -->
|
|
252
265
|
<UCard v-if="trend?.days?.length">
|
|
253
266
|
<div>
|
|
254
|
-
<
|
|
255
|
-
|
|
256
|
-
|
|
267
|
+
<div class="flex items-center justify-between mb-4">
|
|
268
|
+
<p class="text-xs font-semibold text-muted uppercase tracking-wider">
|
|
269
|
+
Daily spend
|
|
270
|
+
</p>
|
|
271
|
+
<USelect
|
|
272
|
+
v-model="trendDays"
|
|
273
|
+
:items="trendDaysOptions"
|
|
274
|
+
size="xs"
|
|
275
|
+
class="min-w-24"
|
|
276
|
+
aria-label="Trend window"
|
|
277
|
+
/>
|
|
278
|
+
</div>
|
|
257
279
|
<div class="flex items-end gap-2 h-32">
|
|
258
280
|
<div
|
|
259
281
|
v-for="day in trend.days"
|
|
@@ -37,6 +37,21 @@ const { data, status, error, refresh } = await fetchApi<DeptDetail>(
|
|
|
37
37
|
`/api/departments/${deptId.value}`,
|
|
38
38
|
)
|
|
39
39
|
|
|
40
|
+
// PR90b v3.32.0 — Compare with another department.
|
|
41
|
+
const { data: deptListData } = fetchApi<{ departments: Array<{ department: string }> }>(
|
|
42
|
+
'/api/departments',
|
|
43
|
+
)
|
|
44
|
+
const compareOptions = computed(() =>
|
|
45
|
+
(deptListData.value?.departments ?? [])
|
|
46
|
+
.filter((d) => d.department !== deptId.value)
|
|
47
|
+
.map((d) => ({
|
|
48
|
+
label: `Compare with ${d.department}`,
|
|
49
|
+
icon: 'i-lucide-columns-2',
|
|
50
|
+
onSelect: () =>
|
|
51
|
+
navigateTo(`/departments/compare?a=${deptId.value}&b=${d.department}`),
|
|
52
|
+
})),
|
|
53
|
+
)
|
|
54
|
+
|
|
40
55
|
const errorMsg = computed(() => data.value?.error || error.value?.message || null)
|
|
41
56
|
const detail = computed<DeptDetail | null>(() => {
|
|
42
57
|
if (!data.value || data.value.error) return null
|
|
@@ -65,6 +80,17 @@ const tierColor = (tier: number | undefined) => {
|
|
|
65
80
|
<template #leading>
|
|
66
81
|
<UButton icon="i-lucide-arrow-left" variant="ghost" size="sm" to="/departments" aria-label="Back" />
|
|
67
82
|
</template>
|
|
83
|
+
<template #right>
|
|
84
|
+
<UDropdownMenu v-if="compareOptions.length > 0" :items="compareOptions">
|
|
85
|
+
<UButton
|
|
86
|
+
label="Compare"
|
|
87
|
+
icon="i-lucide-columns-2"
|
|
88
|
+
variant="soft"
|
|
89
|
+
size="sm"
|
|
90
|
+
trailing-icon="i-lucide-chevron-down"
|
|
91
|
+
/>
|
|
92
|
+
</UDropdownMenu>
|
|
93
|
+
</template>
|
|
68
94
|
</UDashboardNavbar>
|
|
69
95
|
</template>
|
|
70
96
|
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// PR90b v3.32.0 — Compare two departments side-by-side.
|
|
3
|
+
//
|
|
4
|
+
// Driven by `?a=dept1&b=dept2`. Reuses /api/departments/{id}. Shows
|
|
5
|
+
// agent count + tier distribution + workflows count + 30d cost.
|
|
6
|
+
|
|
7
|
+
const route = useRoute()
|
|
8
|
+
const { fetchApi } = useApi()
|
|
9
|
+
|
|
10
|
+
const deptA = computed(() => String(route.query.a ?? ''))
|
|
11
|
+
const deptB = computed(() => String(route.query.b ?? ''))
|
|
12
|
+
|
|
13
|
+
interface AgentLite {
|
|
14
|
+
id: string
|
|
15
|
+
name?: string
|
|
16
|
+
role?: string
|
|
17
|
+
tier?: number
|
|
18
|
+
mbti?: string
|
|
19
|
+
}
|
|
20
|
+
interface WorkflowLite {
|
|
21
|
+
id: string
|
|
22
|
+
name: string
|
|
23
|
+
tier: string
|
|
24
|
+
command: string
|
|
25
|
+
phases_count: number
|
|
26
|
+
}
|
|
27
|
+
interface DeptDetail {
|
|
28
|
+
department: string
|
|
29
|
+
agents: AgentLite[]
|
|
30
|
+
workflows: WorkflowLite[]
|
|
31
|
+
calls_30d: number
|
|
32
|
+
cost_usd_30d: number | null
|
|
33
|
+
error?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { data: a } = fetchApi<DeptDetail>(
|
|
37
|
+
() => deptA.value ? `/api/departments/${deptA.value}` : '',
|
|
38
|
+
)
|
|
39
|
+
const { data: b } = fetchApi<DeptDetail>(
|
|
40
|
+
() => deptB.value ? `/api/departments/${deptB.value}` : '',
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
const errorMsg = computed(() => {
|
|
44
|
+
if (!deptA.value || !deptB.value) return 'Pass ?a=dept1&b=dept2'
|
|
45
|
+
if (a.value?.error) return `Left: ${a.value.error}`
|
|
46
|
+
if (b.value?.error) return `Right: ${b.value.error}`
|
|
47
|
+
return null
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
function diffClass(left: unknown, right: unknown): string {
|
|
51
|
+
return left !== right ? 'bg-yellow-500/10 border-yellow-500/30' : ''
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function formatCost(cost: number | null | undefined): string {
|
|
55
|
+
if (cost === null || cost === undefined) return '—'
|
|
56
|
+
if (cost < 0.01) return '<$0.01'
|
|
57
|
+
if (cost < 1) return `$${cost.toFixed(3)}`
|
|
58
|
+
return `$${cost.toFixed(2)}`
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const tierColor = (tier: number | undefined) => {
|
|
62
|
+
const m: Record<number, 'error' | 'warning' | 'primary' | 'neutral'> = {
|
|
63
|
+
0: 'error', 1: 'warning', 2: 'primary', 3: 'neutral',
|
|
64
|
+
}
|
|
65
|
+
return m[tier ?? 99] ?? 'neutral'
|
|
66
|
+
}
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<template>
|
|
70
|
+
<UDashboardPanel id="departments-compare">
|
|
71
|
+
<template #header>
|
|
72
|
+
<UDashboardNavbar title="Compare departments">
|
|
73
|
+
<template #leading>
|
|
74
|
+
<UButton icon="i-lucide-arrow-left" variant="ghost" size="sm" to="/departments" aria-label="Back" />
|
|
75
|
+
</template>
|
|
76
|
+
<template #trailing>
|
|
77
|
+
<UBadge label="2-way" variant="subtle" size="sm" />
|
|
78
|
+
</template>
|
|
79
|
+
</UDashboardNavbar>
|
|
80
|
+
</template>
|
|
81
|
+
|
|
82
|
+
<template #body>
|
|
83
|
+
<div v-if="errorMsg" class="p-6 text-center text-sm text-error">{{ errorMsg }}</div>
|
|
84
|
+
<div v-else-if="!a || !b" class="p-6 text-center text-sm text-muted">
|
|
85
|
+
<UIcon name="i-lucide-loader-2" class="size-4 animate-spin inline" /> Loading…
|
|
86
|
+
</div>
|
|
87
|
+
<div v-else class="space-y-4 max-w-6xl">
|
|
88
|
+
<section class="grid grid-cols-2 gap-3">
|
|
89
|
+
<NuxtLink :to="`/departments/${a.department}`" class="rounded-lg border border-default p-4 hover:border-primary/40">
|
|
90
|
+
<p class="text-xs text-muted uppercase tracking-wide">Left</p>
|
|
91
|
+
<h2 class="text-xl font-bold capitalize">{{ a.department }}</h2>
|
|
92
|
+
</NuxtLink>
|
|
93
|
+
<NuxtLink :to="`/departments/${b.department}`" class="rounded-lg border border-default p-4 hover:border-primary/40">
|
|
94
|
+
<p class="text-xs text-muted uppercase tracking-wide">Right</p>
|
|
95
|
+
<h2 class="text-xl font-bold capitalize">{{ b.department }}</h2>
|
|
96
|
+
</NuxtLink>
|
|
97
|
+
</section>
|
|
98
|
+
|
|
99
|
+
<h3 class="text-sm font-semibold uppercase tracking-wide text-muted pt-2">Stats</h3>
|
|
100
|
+
<div class="grid grid-cols-2 gap-3">
|
|
101
|
+
<div :class="['rounded-lg border p-3', diffClass(a.agents.length, b.agents.length)]">
|
|
102
|
+
<p class="text-xs text-muted">Agents</p>
|
|
103
|
+
<p class="text-2xl font-bold">{{ a.agents.length }}</p>
|
|
104
|
+
</div>
|
|
105
|
+
<div :class="['rounded-lg border p-3', diffClass(a.agents.length, b.agents.length)]">
|
|
106
|
+
<p class="text-xs text-muted">Agents</p>
|
|
107
|
+
<p class="text-2xl font-bold">{{ b.agents.length }}</p>
|
|
108
|
+
</div>
|
|
109
|
+
<div :class="['rounded-lg border p-3', diffClass(a.workflows.length, b.workflows.length)]">
|
|
110
|
+
<p class="text-xs text-muted">Workflows</p>
|
|
111
|
+
<p class="text-2xl font-bold">{{ a.workflows.length }}</p>
|
|
112
|
+
</div>
|
|
113
|
+
<div :class="['rounded-lg border p-3', diffClass(a.workflows.length, b.workflows.length)]">
|
|
114
|
+
<p class="text-xs text-muted">Workflows</p>
|
|
115
|
+
<p class="text-2xl font-bold">{{ b.workflows.length }}</p>
|
|
116
|
+
</div>
|
|
117
|
+
<div :class="['rounded-lg border p-3', diffClass(a.calls_30d, b.calls_30d)]">
|
|
118
|
+
<p class="text-xs text-muted">Calls (30d)</p>
|
|
119
|
+
<p class="text-2xl font-bold">{{ a.calls_30d }}</p>
|
|
120
|
+
</div>
|
|
121
|
+
<div :class="['rounded-lg border p-3', diffClass(a.calls_30d, b.calls_30d)]">
|
|
122
|
+
<p class="text-xs text-muted">Calls (30d)</p>
|
|
123
|
+
<p class="text-2xl font-bold">{{ b.calls_30d }}</p>
|
|
124
|
+
</div>
|
|
125
|
+
<div :class="['rounded-lg border p-3', diffClass(a.cost_usd_30d, b.cost_usd_30d)]">
|
|
126
|
+
<p class="text-xs text-muted">Cost (30d)</p>
|
|
127
|
+
<p class="text-2xl font-bold">{{ formatCost(a.cost_usd_30d) }}</p>
|
|
128
|
+
</div>
|
|
129
|
+
<div :class="['rounded-lg border p-3', diffClass(a.cost_usd_30d, b.cost_usd_30d)]">
|
|
130
|
+
<p class="text-xs text-muted">Cost (30d)</p>
|
|
131
|
+
<p class="text-2xl font-bold">{{ formatCost(b.cost_usd_30d) }}</p>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<h3 class="text-sm font-semibold uppercase tracking-wide text-muted pt-2">Agents</h3>
|
|
136
|
+
<div class="grid grid-cols-2 gap-3">
|
|
137
|
+
<div class="rounded-lg border border-default p-3 space-y-1">
|
|
138
|
+
<NuxtLink
|
|
139
|
+
v-for="ag in a.agents"
|
|
140
|
+
:key="ag.id"
|
|
141
|
+
:to="`/agents/${ag.id}`"
|
|
142
|
+
class="flex items-center gap-2 text-sm hover:text-primary truncate"
|
|
143
|
+
>
|
|
144
|
+
<UBadge :label="`T${ag.tier}`" :color="tierColor(ag.tier)" variant="subtle" size="xs" />
|
|
145
|
+
<span class="font-medium truncate">{{ ag.name }}</span>
|
|
146
|
+
<span class="text-xs text-muted truncate">— {{ ag.role }}</span>
|
|
147
|
+
</NuxtLink>
|
|
148
|
+
</div>
|
|
149
|
+
<div class="rounded-lg border border-default p-3 space-y-1">
|
|
150
|
+
<NuxtLink
|
|
151
|
+
v-for="ag in b.agents"
|
|
152
|
+
:key="ag.id"
|
|
153
|
+
:to="`/agents/${ag.id}`"
|
|
154
|
+
class="flex items-center gap-2 text-sm hover:text-primary truncate"
|
|
155
|
+
>
|
|
156
|
+
<UBadge :label="`T${ag.tier}`" :color="tierColor(ag.tier)" variant="subtle" size="xs" />
|
|
157
|
+
<span class="font-medium truncate">{{ ag.name }}</span>
|
|
158
|
+
<span class="text-xs text-muted truncate">— {{ ag.role }}</span>
|
|
159
|
+
</NuxtLink>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<h3
|
|
164
|
+
v-if="a.workflows.length > 0 || b.workflows.length > 0"
|
|
165
|
+
class="text-sm font-semibold uppercase tracking-wide text-muted pt-2"
|
|
166
|
+
>
|
|
167
|
+
Workflows
|
|
168
|
+
</h3>
|
|
169
|
+
<div v-if="a.workflows.length > 0 || b.workflows.length > 0" class="grid grid-cols-2 gap-3">
|
|
170
|
+
<div class="rounded-lg border border-default p-3 space-y-1">
|
|
171
|
+
<p v-for="w in a.workflows" :key="w.id" class="text-sm truncate">
|
|
172
|
+
<span class="font-mono text-xs text-muted">{{ w.command || w.id }}</span>
|
|
173
|
+
· {{ w.name }}
|
|
174
|
+
</p>
|
|
175
|
+
<p v-if="!a.workflows.length" class="text-sm text-muted italic">No workflows</p>
|
|
176
|
+
</div>
|
|
177
|
+
<div class="rounded-lg border border-default p-3 space-y-1">
|
|
178
|
+
<p v-for="w in b.workflows" :key="w.id" class="text-sm truncate">
|
|
179
|
+
<span class="font-mono text-xs text-muted">{{ w.command || w.id }}</span>
|
|
180
|
+
· {{ w.name }}
|
|
181
|
+
</p>
|
|
182
|
+
<p v-if="!b.workflows.length" class="text-sm text-muted italic">No workflows</p>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<p class="text-xs text-muted pt-4 italic">
|
|
187
|
+
Cells with a yellow tint differ between the two departments.
|
|
188
|
+
</p>
|
|
189
|
+
</div>
|
|
190
|
+
</template>
|
|
191
|
+
</UDashboardPanel>
|
|
192
|
+
</template>
|
package/package.json
CHANGED