@nuxtagentic/automations 0.0.0-stage → 0.1.1
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.md +9 -0
- package/README.md +173 -2
- package/client.ts +24 -0
- package/module.ts +55 -0
- package/package.json +59 -3
- package/runtime/components/AutomationCanvas.vue +488 -0
- package/runtime/components/AutomationInspector.vue +436 -0
- package/runtime/components/AutomationNodeIcon.vue +27 -0
- package/runtime/components/AutomationRunsPanel.vue +475 -0
- package/runtime/components/AutomationSidebar.vue +253 -0
- package/runtime/components/AutomationStatusIcon.vue +44 -0
- package/runtime/pages/automations.vue +1181 -0
- package/runtime/server/api/automations/[id]/endpoint.get.ts +12 -0
- package/runtime/server/api/automations/[id]/run.post.ts +30 -0
- package/runtime/server/api/automations/events.post.ts +37 -0
- package/runtime/server/api/automations/runs/[id]/resume.post.ts +23 -0
- package/runtime/server/handlers/automation-hook.post.ts +51 -0
- package/runtime/server/handlers/connect-hook.post.ts +54 -0
- package/runtime/server/handlers/integration-hook.post.ts +86 -0
- package/runtime/server/plugins/events.ts +79 -0
- package/runtime/server/plugins/maintenance.ts +20 -0
- package/runtime/server/utils/agent-runner.ts +15 -0
- package/runtime/server/utils/automation-context.ts +77 -0
- package/runtime/server/utils/automation-engine.ts +697 -0
- package/runtime/server/utils/automation-launch.ts +205 -0
- package/runtime/server/utils/automation-schedule.ts +31 -0
- package/runtime/server/utils/connection-events.ts +114 -0
- package/runtime/server/utils/maintenance.ts +255 -0
- package/runtime/server/utils/schedule-sync.ts +32 -0
- package/runtime/server/utils/transient-retry.ts +34 -0
- package/runtime/server/workflows/runner.ts +39 -0
- package/runtime/shared/agent.ts +5 -0
- package/runtime/shared/automation-run.ts +61 -0
- package/runtime/shared/automation.ts +202 -0
- package/runtime/workspace.ts +56 -0
- package/schema.ts +222 -0
- package/server.ts +2 -0
- package/workflow.ts +2 -0
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, onBeforeUnmount, ref, shallowRef, watch } from 'vue'
|
|
3
|
+
import type { InferSelect } from '@cloudconfig/core'
|
|
4
|
+
import type { AutomationsConfig } from '../../schema'
|
|
5
|
+
import type { AutomationNode, AutomationStep } from '../shared/automation'
|
|
6
|
+
import {
|
|
7
|
+
formatElapsedDuration,
|
|
8
|
+
getStatusColor,
|
|
9
|
+
formatStepDuration,
|
|
10
|
+
getStepLogs,
|
|
11
|
+
getStepStatus,
|
|
12
|
+
} from '../shared/automation-run'
|
|
13
|
+
import AutomationStatusIcon from './AutomationStatusIcon.vue'
|
|
14
|
+
|
|
15
|
+
type Run = InferSelect<AutomationsConfig, 'automationRun'>
|
|
16
|
+
const { runs, total, hasMore, selectedRun, defaultSource, loading, running } = defineProps<{
|
|
17
|
+
runs: Run[]
|
|
18
|
+
total: number
|
|
19
|
+
hasMore: boolean
|
|
20
|
+
selectedRun: Run | null
|
|
21
|
+
defaultSource: string
|
|
22
|
+
loading: boolean
|
|
23
|
+
running: boolean
|
|
24
|
+
}>()
|
|
25
|
+
const emit = defineEmits<{
|
|
26
|
+
select: [id: number]
|
|
27
|
+
back: []
|
|
28
|
+
close: []
|
|
29
|
+
rerun: [run: Run]
|
|
30
|
+
loadMore: []
|
|
31
|
+
}>()
|
|
32
|
+
// Retain the detail while the track slides back to the list.
|
|
33
|
+
const lastRun = shallowRef<Run | null>(selectedRun)
|
|
34
|
+
const detail = computed(() => selectedRun ?? lastRun.value)
|
|
35
|
+
const expanded = ref<Partial<Record<AutomationNode, boolean>>>({})
|
|
36
|
+
const copyLabel = ref('Copy')
|
|
37
|
+
let copyTimer: ReturnType<typeof setTimeout> | undefined
|
|
38
|
+
watch(
|
|
39
|
+
() => selectedRun,
|
|
40
|
+
(run) => {
|
|
41
|
+
if (!run) {
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
if (lastRun.value?.id !== run.id) {
|
|
45
|
+
expanded.value = {}
|
|
46
|
+
copyLabel.value = 'Copy'
|
|
47
|
+
}
|
|
48
|
+
lastRun.value = run
|
|
49
|
+
},
|
|
50
|
+
{ immediate: true },
|
|
51
|
+
)
|
|
52
|
+
onBeforeUnmount(() => clearTimeout(copyTimer))
|
|
53
|
+
function selectRun(id: number) {
|
|
54
|
+
expanded.value = {}
|
|
55
|
+
emit('select', id)
|
|
56
|
+
}
|
|
57
|
+
const nodes = [
|
|
58
|
+
{ id: 'trigger', label: 'Trigger' },
|
|
59
|
+
{ id: 'agent', label: 'Agent call' },
|
|
60
|
+
{ id: 'success', label: 'On success' },
|
|
61
|
+
{ id: 'error', label: 'On error' },
|
|
62
|
+
] as const
|
|
63
|
+
const steps = computed(() =>
|
|
64
|
+
nodes.map((node) => {
|
|
65
|
+
const step = (detail.value?.steps as AutomationStep[] | undefined)?.find(
|
|
66
|
+
(entry) => entry.node === node.id,
|
|
67
|
+
)
|
|
68
|
+
const run = detail.value
|
|
69
|
+
const details: Record<string, string> = {
|
|
70
|
+
...(node.id === 'trigger' && run ? { 'Event ID': run.eventId } : {}),
|
|
71
|
+
...(node.id === 'agent' && run ? { Attempts: String(run.attempts) } : {}),
|
|
72
|
+
...step?.details,
|
|
73
|
+
...(step?.startedAt ? { Started: new Date(step.startedAt).toLocaleString() } : {}),
|
|
74
|
+
...(step?.finishedAt ? { Finished: new Date(step.finishedAt).toLocaleString() } : {}),
|
|
75
|
+
}
|
|
76
|
+
return { ...node, step, details, logs: getStepLogs(step), status: getStepStatus(step) }
|
|
77
|
+
}),
|
|
78
|
+
)
|
|
79
|
+
function statusLabel(status: Run['status']) {
|
|
80
|
+
return {
|
|
81
|
+
queued: 'Queued',
|
|
82
|
+
running: 'Running',
|
|
83
|
+
waiting: 'Waiting for approval',
|
|
84
|
+
succeeded: 'Succeeded',
|
|
85
|
+
failed: 'Failed',
|
|
86
|
+
}[status]
|
|
87
|
+
}
|
|
88
|
+
function time(value: string | null | undefined) {
|
|
89
|
+
if (!value) {
|
|
90
|
+
return '—'
|
|
91
|
+
}
|
|
92
|
+
return new Date(value).toLocaleTimeString([], {
|
|
93
|
+
hour: '2-digit',
|
|
94
|
+
minute: '2-digit',
|
|
95
|
+
second: '2-digit',
|
|
96
|
+
hour12: false,
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
function duration(run: Run) {
|
|
100
|
+
return formatElapsedDuration(run.startedAt, run.finishedAt) || '…'
|
|
101
|
+
}
|
|
102
|
+
function ago(value: string | null | undefined) {
|
|
103
|
+
if (!value) {
|
|
104
|
+
return 'Just now'
|
|
105
|
+
}
|
|
106
|
+
const minutes = Math.max(0, Math.floor((Date.now() - new Date(value).getTime()) / 60000))
|
|
107
|
+
if (minutes < 1) {
|
|
108
|
+
return 'Just now'
|
|
109
|
+
}
|
|
110
|
+
if (minutes < 60) {
|
|
111
|
+
return `${minutes}m ago`
|
|
112
|
+
}
|
|
113
|
+
if (minutes < 1440) {
|
|
114
|
+
return `${Math.floor(minutes / 60)}h ago`
|
|
115
|
+
}
|
|
116
|
+
return `${Math.floor(minutes / 1440)}d ago`
|
|
117
|
+
}
|
|
118
|
+
function isTest(run: Run) {
|
|
119
|
+
return run.eventId.replace(/^\d+:/, '').startsWith('test:')
|
|
120
|
+
}
|
|
121
|
+
function source(run: Run, withTest = false) {
|
|
122
|
+
const trigger = (run.steps as AutomationStep[]).find((step) => step.node === 'trigger')
|
|
123
|
+
const event =
|
|
124
|
+
trigger?.summary ??
|
|
125
|
+
trigger?.message.match(/^Received (.+)\.$/)?.[1] ??
|
|
126
|
+
(run.eventId.replace(/^\d+:/, '').startsWith('webhook:') ? 'Webhook' : defaultSource)
|
|
127
|
+
return `${withTest && isTest(run) ? 'Test · ' : ''}${event}`
|
|
128
|
+
}
|
|
129
|
+
function summary(node: AutomationNode, step: AutomationStep | undefined) {
|
|
130
|
+
if (step?.status === 'skipped') {
|
|
131
|
+
return 'Skipped'
|
|
132
|
+
}
|
|
133
|
+
if (step?.summary) {
|
|
134
|
+
return step.summary
|
|
135
|
+
}
|
|
136
|
+
if (node === 'trigger' && detail.value) {
|
|
137
|
+
return source(detail.value)
|
|
138
|
+
}
|
|
139
|
+
return step?.message ?? (detail.value?.finishedAt ? 'Skipped' : 'Waiting')
|
|
140
|
+
}
|
|
141
|
+
function elapsed(at: string, startedAt: string | null) {
|
|
142
|
+
if (!startedAt) {
|
|
143
|
+
return time(at)
|
|
144
|
+
}
|
|
145
|
+
const seconds = (new Date(at).getTime() - new Date(startedAt).getTime()) / 1000
|
|
146
|
+
return `${seconds >= 0 ? '+' : ''}${seconds.toFixed(2)}`
|
|
147
|
+
}
|
|
148
|
+
function toggleStep(node: AutomationNode) {
|
|
149
|
+
expanded.value = { ...expanded.value, [node]: !expanded.value[node] }
|
|
150
|
+
}
|
|
151
|
+
function getLogColor({ status, level }: NonNullable<AutomationStep['logs']>[number]) {
|
|
152
|
+
if (status === 'failed' || level === 'error') {
|
|
153
|
+
return 'text-error'
|
|
154
|
+
}
|
|
155
|
+
return level === 'warning' ? 'text-warning' : 'text-(--gray-1000)'
|
|
156
|
+
}
|
|
157
|
+
async function copyOutput() {
|
|
158
|
+
if (!detail.value?.output) {
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
clearTimeout(copyTimer)
|
|
162
|
+
try {
|
|
163
|
+
await navigator.clipboard.writeText(detail.value.output)
|
|
164
|
+
copyLabel.value = 'Copied'
|
|
165
|
+
} catch {
|
|
166
|
+
copyLabel.value = 'Copy failed'
|
|
167
|
+
}
|
|
168
|
+
copyTimer = setTimeout(() => {
|
|
169
|
+
copyLabel.value = 'Copy'
|
|
170
|
+
}, 1400)
|
|
171
|
+
}
|
|
172
|
+
</script>
|
|
173
|
+
|
|
174
|
+
<template>
|
|
175
|
+
<div class="h-full w-full overflow-hidden">
|
|
176
|
+
<div
|
|
177
|
+
class="flex h-full w-[200%] transition-transform duration-260 ease-[cubic-bezier(.2,0,0,1)] motion-reduce:transition-none"
|
|
178
|
+
:class="selectedRun ? '-translate-x-1/2' : 'translate-x-0'"
|
|
179
|
+
>
|
|
180
|
+
<section
|
|
181
|
+
class="flex h-full w-1/2 min-w-0 flex-col"
|
|
182
|
+
:inert="!!selectedRun"
|
|
183
|
+
>
|
|
184
|
+
<div class="flex h-13 shrink-0 items-center gap-2.5 border-b border-(--gray-400) pr-2 pl-4">
|
|
185
|
+
<span class="text-[15px] text-highlighted">Runs</span>
|
|
186
|
+
<span class="flex-1 text-xs text-dimmed">
|
|
187
|
+
{{ Math.max(total, runs.length) }}
|
|
188
|
+
{{ Math.max(total, runs.length) === 1 ? 'run' : 'runs' }}
|
|
189
|
+
</span>
|
|
190
|
+
<UButton
|
|
191
|
+
icon="i-lucide-x"
|
|
192
|
+
aria-label="Close runs"
|
|
193
|
+
color="neutral"
|
|
194
|
+
variant="ghost"
|
|
195
|
+
class="size-8 justify-center rounded-md p-0 text-muted hover:bg-(--gray-400) hover:text-highlighted"
|
|
196
|
+
:ui="{ leadingIcon: 'size-4' }"
|
|
197
|
+
@click="emit('close')"
|
|
198
|
+
/>
|
|
199
|
+
</div>
|
|
200
|
+
<div
|
|
201
|
+
class="flex min-h-0 flex-1 flex-col gap-0.5 overflow-x-hidden overflow-y-auto p-2 pb-[120px]"
|
|
202
|
+
>
|
|
203
|
+
<div
|
|
204
|
+
v-if="loading && !runs.length"
|
|
205
|
+
class="flex justify-center p-8"
|
|
206
|
+
>
|
|
207
|
+
<AutomationStatusIcon
|
|
208
|
+
status="running"
|
|
209
|
+
class="size-4"
|
|
210
|
+
/>
|
|
211
|
+
</div>
|
|
212
|
+
<p
|
|
213
|
+
v-else-if="!runs.length"
|
|
214
|
+
class="px-4 py-8 text-center text-[13px] text-dimmed"
|
|
215
|
+
>
|
|
216
|
+
No runs yet. Use Test run to try it.
|
|
217
|
+
</p>
|
|
218
|
+
<button
|
|
219
|
+
v-for="run in runs"
|
|
220
|
+
:key="run.id"
|
|
221
|
+
type="button"
|
|
222
|
+
class="flex min-h-14 w-full shrink-0 items-center gap-3 rounded-lg px-2.5 py-2 text-left transition-colors duration-160 hover:bg-(--gray-400) active:scale-[0.98] focus-visible:outline-2 focus-visible:outline-primary motion-reduce:transition-none"
|
|
223
|
+
:class="selectedRun?.id === run.id && 'bg-(--gray-400)'"
|
|
224
|
+
@click="selectRun(run.id)"
|
|
225
|
+
>
|
|
226
|
+
<AutomationStatusIcon
|
|
227
|
+
:status="run.status"
|
|
228
|
+
class="size-4"
|
|
229
|
+
/>
|
|
230
|
+
<span class="flex min-w-0 flex-1 flex-col gap-0.5">
|
|
231
|
+
<span class="flex items-center gap-2 text-sm leading-[18px] text-highlighted">
|
|
232
|
+
Run #{{ run.id }}
|
|
233
|
+
<span
|
|
234
|
+
v-if="isTest(run)"
|
|
235
|
+
class="flex h-[18px] items-center rounded-[5px] border border-(--gray-500) px-1.5 text-[11px] text-muted"
|
|
236
|
+
>
|
|
237
|
+
Test
|
|
238
|
+
</span>
|
|
239
|
+
</span>
|
|
240
|
+
<span class="truncate text-xs leading-4 text-dimmed">
|
|
241
|
+
{{ statusLabel(run.status) }}{{ run.finishedAt ? ` · ${duration(run)}` : '' }} ·
|
|
242
|
+
{{ source(run) }}
|
|
243
|
+
</span>
|
|
244
|
+
</span>
|
|
245
|
+
<span class="shrink-0 text-xs tabular-nums text-dimmed">{{ ago(run.startedAt) }}</span>
|
|
246
|
+
</button>
|
|
247
|
+
<UButton
|
|
248
|
+
v-if="hasMore"
|
|
249
|
+
label="Load more runs"
|
|
250
|
+
color="neutral"
|
|
251
|
+
variant="ghost"
|
|
252
|
+
class="mt-2 w-full justify-center text-xs"
|
|
253
|
+
:loading="loading"
|
|
254
|
+
@click="emit('loadMore')"
|
|
255
|
+
/>
|
|
256
|
+
</div>
|
|
257
|
+
</section>
|
|
258
|
+
<section
|
|
259
|
+
class="flex h-full w-1/2 min-w-0 flex-col"
|
|
260
|
+
:inert="!selectedRun"
|
|
261
|
+
>
|
|
262
|
+
<div class="flex h-13 shrink-0 items-center gap-1.5 border-b border-(--gray-400) px-2">
|
|
263
|
+
<UButton
|
|
264
|
+
icon="i-lucide-chevron-left"
|
|
265
|
+
aria-label="All runs"
|
|
266
|
+
title="All runs"
|
|
267
|
+
color="neutral"
|
|
268
|
+
variant="ghost"
|
|
269
|
+
class="size-8 justify-center rounded-md p-0 text-muted hover:bg-(--gray-400) hover:text-highlighted"
|
|
270
|
+
:ui="{ leadingIcon: 'size-4' }"
|
|
271
|
+
@click="emit('back')"
|
|
272
|
+
/>
|
|
273
|
+
<span class="text-[15px] text-highlighted">Run #{{ detail?.id }}</span>
|
|
274
|
+
<span
|
|
275
|
+
v-if="detail"
|
|
276
|
+
class="flex min-w-0 flex-1 items-center gap-1.5 text-xs"
|
|
277
|
+
:class="getStatusColor(detail.status)"
|
|
278
|
+
>
|
|
279
|
+
<AutomationStatusIcon
|
|
280
|
+
:status="detail.status"
|
|
281
|
+
class="size-3.5"
|
|
282
|
+
/>
|
|
283
|
+
{{ statusLabel(detail.status) }}
|
|
284
|
+
</span>
|
|
285
|
+
<UButton
|
|
286
|
+
icon="i-lucide-x"
|
|
287
|
+
aria-label="Close runs"
|
|
288
|
+
color="neutral"
|
|
289
|
+
variant="ghost"
|
|
290
|
+
class="size-8 justify-center rounded-md p-0 text-muted hover:bg-(--gray-400) hover:text-highlighted"
|
|
291
|
+
:ui="{ leadingIcon: 'size-4' }"
|
|
292
|
+
@click="emit('close')"
|
|
293
|
+
/>
|
|
294
|
+
</div>
|
|
295
|
+
<div
|
|
296
|
+
v-if="detail"
|
|
297
|
+
class="flex min-h-0 flex-1 flex-col gap-6 overflow-x-hidden overflow-y-auto p-4 pb-[120px]"
|
|
298
|
+
>
|
|
299
|
+
<div class="flex flex-wrap gap-x-3 gap-y-1.5 text-xs">
|
|
300
|
+
<span
|
|
301
|
+
class="flex flex-col gap-0.5"
|
|
302
|
+
:title="detail.startedAt ? new Date(detail.startedAt).toLocaleString() : undefined"
|
|
303
|
+
>
|
|
304
|
+
<span class="text-dimmed">Started</span>
|
|
305
|
+
<span class="tabular-nums text-highlighted">{{ time(detail.startedAt) }}</span>
|
|
306
|
+
</span>
|
|
307
|
+
<span
|
|
308
|
+
class="flex flex-col gap-0.5"
|
|
309
|
+
:title="
|
|
310
|
+
detail.finishedAt
|
|
311
|
+
? `Finished: ${new Date(detail.finishedAt).toLocaleString()}`
|
|
312
|
+
: undefined
|
|
313
|
+
"
|
|
314
|
+
>
|
|
315
|
+
<span class="text-dimmed">Duration</span>
|
|
316
|
+
<span class="tabular-nums text-highlighted">{{ duration(detail) }}</span>
|
|
317
|
+
</span>
|
|
318
|
+
<span class="flex min-w-0 flex-col gap-0.5">
|
|
319
|
+
<span class="text-dimmed">Source</span>
|
|
320
|
+
<span class="break-words font-mono text-highlighted">{{ source(detail, true) }}</span>
|
|
321
|
+
</span>
|
|
322
|
+
</div>
|
|
323
|
+
<div class="flex flex-col gap-2">
|
|
324
|
+
<span class="text-xs text-dimmed">Steps</span>
|
|
325
|
+
<div class="flex flex-col">
|
|
326
|
+
<div
|
|
327
|
+
v-for="(item, index) in steps"
|
|
328
|
+
:key="item.id"
|
|
329
|
+
class="grid grid-cols-[24px_minmax(0,1fr)] gap-x-2.5 transition-opacity duration-240 motion-reduce:transition-none"
|
|
330
|
+
:class="(!item.step || item.step.status === 'skipped') && 'opacity-50'"
|
|
331
|
+
>
|
|
332
|
+
<div class="flex flex-col items-center pt-2.5">
|
|
333
|
+
<AutomationStatusIcon
|
|
334
|
+
:status="item.status ?? 'skipped'"
|
|
335
|
+
class="size-4"
|
|
336
|
+
/>
|
|
337
|
+
<span
|
|
338
|
+
v-if="index < steps.length - 1"
|
|
339
|
+
class="mt-1.5 w-px flex-1 bg-(--gray-500)"
|
|
340
|
+
/>
|
|
341
|
+
</div>
|
|
342
|
+
<div class="min-w-0 pb-1.5">
|
|
343
|
+
<button
|
|
344
|
+
type="button"
|
|
345
|
+
class="-ml-1.5 flex min-h-9 w-full items-center gap-2 rounded-md px-1.5 py-1 text-left enabled:hover:bg-(--gray-300) focus-visible:outline-2 focus-visible:outline-primary disabled:cursor-default"
|
|
346
|
+
:disabled="!item.logs.length"
|
|
347
|
+
:aria-expanded="!!expanded[item.id]"
|
|
348
|
+
@click="toggleStep(item.id)"
|
|
349
|
+
>
|
|
350
|
+
<span class="flex min-w-0 flex-1 flex-col gap-px">
|
|
351
|
+
<span class="text-[13px] text-highlighted">{{ item.label }}</span>
|
|
352
|
+
<span class="truncate text-xs text-muted">
|
|
353
|
+
{{ summary(item.id, item.step) }}
|
|
354
|
+
</span>
|
|
355
|
+
</span>
|
|
356
|
+
<span class="shrink-0 text-xs tabular-nums text-dimmed">
|
|
357
|
+
{{ formatStepDuration(item.step) }}
|
|
358
|
+
</span>
|
|
359
|
+
<UIcon
|
|
360
|
+
name="i-lucide-chevron-down"
|
|
361
|
+
class="size-3.5 shrink-0 text-dimmed transition-[transform,opacity] duration-220 motion-reduce:transition-none"
|
|
362
|
+
:class="[expanded[item.id] && 'rotate-180', !item.logs.length && 'opacity-0']"
|
|
363
|
+
/>
|
|
364
|
+
</button>
|
|
365
|
+
<div
|
|
366
|
+
class="grid transition-[grid-template-rows] duration-240 ease-[cubic-bezier(.2,0,0,1)] motion-reduce:transition-none"
|
|
367
|
+
:class="expanded[item.id] ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'"
|
|
368
|
+
>
|
|
369
|
+
<div
|
|
370
|
+
class="min-h-0 overflow-hidden"
|
|
371
|
+
:inert="!expanded[item.id]"
|
|
372
|
+
>
|
|
373
|
+
<div
|
|
374
|
+
class="mt-1 mb-1.5 flex flex-col gap-1 rounded-lg border border-(--gray-400) bg-(--gray-100) px-2.5 py-2 font-mono text-[11px] leading-[17px]"
|
|
375
|
+
>
|
|
376
|
+
<div
|
|
377
|
+
v-for="(log, logIndex) in item.logs"
|
|
378
|
+
:key="logIndex"
|
|
379
|
+
class="grid grid-cols-[42px_minmax(0,1fr)] gap-2"
|
|
380
|
+
>
|
|
381
|
+
<span
|
|
382
|
+
class="tabular-nums text-dimmed"
|
|
383
|
+
:title="log.at"
|
|
384
|
+
>
|
|
385
|
+
{{ elapsed(log.at, detail.startedAt) }}
|
|
386
|
+
</span>
|
|
387
|
+
<span
|
|
388
|
+
class="break-words"
|
|
389
|
+
:class="getLogColor(log)"
|
|
390
|
+
>
|
|
391
|
+
{{ log.message }}
|
|
392
|
+
</span>
|
|
393
|
+
</div>
|
|
394
|
+
<dl
|
|
395
|
+
v-if="Object.keys(item.details).length"
|
|
396
|
+
class="mt-1 flex flex-col gap-1 border-t border-(--gray-400) pt-2"
|
|
397
|
+
>
|
|
398
|
+
<div
|
|
399
|
+
v-for="(value, label) in item.details"
|
|
400
|
+
:key="label"
|
|
401
|
+
class="flex flex-col"
|
|
402
|
+
>
|
|
403
|
+
<dt class="text-dimmed">{{ label }}</dt>
|
|
404
|
+
<dd class="m-0 break-words whitespace-pre-wrap text-(--gray-1000)">
|
|
405
|
+
{{ value }}
|
|
406
|
+
</dd>
|
|
407
|
+
</div>
|
|
408
|
+
</dl>
|
|
409
|
+
</div>
|
|
410
|
+
</div>
|
|
411
|
+
</div>
|
|
412
|
+
</div>
|
|
413
|
+
</div>
|
|
414
|
+
</div>
|
|
415
|
+
</div>
|
|
416
|
+
<div
|
|
417
|
+
v-if="detail.output"
|
|
418
|
+
class="flex flex-col gap-2"
|
|
419
|
+
>
|
|
420
|
+
<span class="text-xs text-dimmed">Agent response</span>
|
|
421
|
+
<div class="overflow-hidden rounded-[10px] border border-(--gray-400)">
|
|
422
|
+
<div
|
|
423
|
+
class="px-3.5 py-3 text-[13px] leading-5 break-words whitespace-pre-wrap text-default [text-wrap:pretty]"
|
|
424
|
+
>
|
|
425
|
+
{{ detail.output }}
|
|
426
|
+
</div>
|
|
427
|
+
<div class="border-t border-(--gray-400) p-2">
|
|
428
|
+
<UButton
|
|
429
|
+
:label="copyLabel"
|
|
430
|
+
color="neutral"
|
|
431
|
+
variant="soft"
|
|
432
|
+
class="h-7 rounded-md bg-(--gray-400) px-2.5 text-xs font-normal text-highlighted hover:bg-(--gray-500)"
|
|
433
|
+
@click="copyOutput"
|
|
434
|
+
/>
|
|
435
|
+
</div>
|
|
436
|
+
</div>
|
|
437
|
+
</div>
|
|
438
|
+
<div
|
|
439
|
+
v-if="detail.error"
|
|
440
|
+
class="flex flex-col gap-2"
|
|
441
|
+
>
|
|
442
|
+
<span class="text-xs text-dimmed">Error</span>
|
|
443
|
+
<div
|
|
444
|
+
class="flex items-start gap-2.5 rounded-[10px] border border-(--gray-400) px-3 py-2.5"
|
|
445
|
+
>
|
|
446
|
+
<UIcon
|
|
447
|
+
name="i-lucide-circle-alert"
|
|
448
|
+
class="size-3.5 shrink-0 text-error"
|
|
449
|
+
/>
|
|
450
|
+
<span class="break-words font-mono text-xs leading-[18px] text-highlighted">
|
|
451
|
+
{{ detail.error }}
|
|
452
|
+
</span>
|
|
453
|
+
</div>
|
|
454
|
+
</div>
|
|
455
|
+
<div class="flex flex-col gap-2">
|
|
456
|
+
<span class="text-xs text-dimmed">Input</span>
|
|
457
|
+
<pre
|
|
458
|
+
class="m-0 rounded-lg border border-(--gray-400) bg-(--gray-100) p-3 font-mono text-xs leading-[18px] break-words whitespace-pre-wrap text-(--gray-1000)"
|
|
459
|
+
>{{ JSON.stringify(detail.payload, null, 2) ?? '{}' }}</pre>
|
|
460
|
+
</div>
|
|
461
|
+
<UButton
|
|
462
|
+
label="Re-run with this input"
|
|
463
|
+
icon="i-lucide-rotate-ccw"
|
|
464
|
+
color="neutral"
|
|
465
|
+
variant="outline"
|
|
466
|
+
class="h-8 self-start gap-1.5 rounded-lg px-3 text-[13px] font-normal text-highlighted ring-(--gray-500) hover:bg-(--gray-400) active:scale-[0.98]"
|
|
467
|
+
:ui="{ leadingIcon: 'size-[13px]' }"
|
|
468
|
+
:disabled="running"
|
|
469
|
+
@click="emit('rerun', detail)"
|
|
470
|
+
/>
|
|
471
|
+
</div>
|
|
472
|
+
</section>
|
|
473
|
+
</div>
|
|
474
|
+
</div>
|
|
475
|
+
</template>
|