| 1 | <template> |
| 2 | <div> |
| 3 | <CardEntity :embedded hoverable clickable @click.stop="showDetails = true"> |
| 4 | <template #headerMain> |
| 5 | <div class="flex items-center gap-2"> |
| 6 | {{ flow.session_id }} |
| 7 | </div> |
| 8 | </template> |
| 9 | <template #headerExtra> |
| 10 | <n-popover overlap placement="top-end" class="max-h-64" scrollable to="body"> |
| 11 | <template #trigger> |
| 12 | <div class="flex cursor-help items-center gap-2"> |
| 13 | <span> |
| 14 | {{ formatDate(flow.start_time, dFormats.datetimesec) }} |
| 15 | </span> |
| 16 | <Icon :name="TimeIcon" :size="16" /> |
| 17 | </div> |
| 18 | </template> |
| 19 | <div class="flex flex-col px-1 py-2"> |
| 20 | <AgentFlowTimeline :flow /> |
| 21 | </div> |
| 22 | </n-popover> |
| 23 | </template> |
| 24 | <template #default> |
| 25 | <div class="flex flex-wrap gap-2"> |
| 26 | <Badge |
| 27 | v-for="artifact of flow.artifacts_with_results" |
| 28 | :key="artifact" |
| 29 | color="primary" |
| 30 | type="splitted" |
| 31 | > |
| 32 | <template #value> |
| 33 | {{ artifact }} |
| 34 | </template> |
| 35 | </Badge> |
| 36 | </div> |
| 37 | </template> |
| 38 | <template #mainExtra> |
| 39 | <div class="flex flex-wrap items-center gap-3"> |
| 40 | <Badge type="splitted" color="primary"> |
| 41 | <template #label>State</template> |
| 42 | <template #value> |
| 43 | {{ flow.state || "-" }} |
| 44 | </template> |
| 45 | </Badge> |
| 46 | <Badge type="splitted" color="primary"> |
| 47 | <template #label>Status</template> |
| 48 | <template #value> |
| 49 | {{ flow.status || "-" }} |
| 50 | </template> |
| 51 | </Badge> |
| 52 | <Badge type="splitted" color="primary"> |
| 53 | <template #label>Exec. time</template> |
| 54 | <template #value> |
| 55 | {{ executionDuration }} |
| 56 | </template> |
| 57 | </Badge> |
| 58 | <Badge :type="flow.dirty ? 'active' : 'muted'"> |
| 59 | <template #iconRight> |
| 60 | <Icon :name="flow.dirty ? EnabledIcon : DisabledIcon" :size="14" /> |
| 61 | </template> |
| 62 | <template #label>Dirty</template> |
| 63 | </Badge> |
| 64 | <Badge :type="flow.user_notified ? 'active' : 'muted'"> |
| 65 | <template #iconRight> |
| 66 | <Icon :name="flow.user_notified ? EnabledIcon : DisabledIcon" :size="14" /> |
| 67 | </template> |
| 68 | <template #label>User notified</template> |
| 69 | </Badge> |
| 70 | </div> |
| 71 | </template> |
| 72 | </CardEntity> |
| 73 | |
| 74 | <n-modal |
| 75 | v-model:show="showDetails" |
| 76 | preset="card" |
| 77 | content-class="p-0!" |
| 78 | :style="{ maxWidth: 'min(800px, 90vw)', minHeight: 'min(550px, 90vh)', overflow: 'hidden' }" |
| 79 | :title="`Agent Flow: ${flow.session_id}`" |
| 80 | :bordered="false" |
| 81 | segmented |
| 82 | > |
| 83 | <n-tabs type="line" animated :tabs-padding="24"> |
| 84 | <n-tab-pane name="Info" tab="Info" display-directive="show"> |
| 85 | <div v-if="properties" class="grid-auto-fit-200 grid gap-2 p-6 pt-3"> |
| 86 | <CardKV v-for="(value, key) of properties" :key> |
| 87 | <template #key> |
| 88 | {{ key }} |
| 89 | </template> |
| 90 | <template #value> |
| 91 | {{ value === "" ? "-" : (value ?? "-") }} |
| 92 | </template> |
| 93 | </CardKV> |
| 94 | </div> |
| 95 | </n-tab-pane> |
| 96 | <n-tab-pane name="Backtrace" tab="Backtrace" display-directive="show"> |
| 97 | <div class="p-6 pt-3"> |
| 98 | <n-input |
| 99 | :value="flow.backtrace" |
| 100 | type="textarea" |
| 101 | readonly |
| 102 | placeholder="Empty" |
| 103 | size="large" |
| 104 | :autosize="{ |
| 105 | minRows: 3, |
| 106 | maxRows: 18 |
| 107 | }" |
| 108 | /> |
| 109 | </div> |
| 110 | </n-tab-pane> |
| 111 | <n-tab-pane name="Timeline" tab="Timeline" display-directive="show:lazy"> |
| 112 | <div class="p-6 pt-3"> |
| 113 | <AgentFlowTimeline :flow /> |
| 114 | </div> |
| 115 | </n-tab-pane> |
| 116 | <n-tab-pane name="Logs" tab="Logs" display-directive="show:lazy"> |
| 117 | <div v-if="flow.logs.length" class="p-6 pt-3"> |
| 118 | <ul> |
| 119 | <li v-for="log of flow.logs" :key="log"> |
| 120 | {{ log }} |
| 121 | </li> |
| 122 | </ul> |
| 123 | </div> |
| 124 | <n-empty v-else description="No items found" class="h-48 justify-center" /> |
| 125 | </n-tab-pane> |
| 126 | <n-tab-pane name="Uploaded files" tab="Uploaded files" display-directive="show:lazy"> |
| 127 | <div v-if="flow.uploaded_files.length" class="p-6 pt-3"> |
| 128 | <ul> |
| 129 | <li v-for="file of flow.uploaded_files" :key="file"> |
| 130 | {{ file }} |
| 131 | </li> |
| 132 | </ul> |
| 133 | </div> |
| 134 | <n-empty v-else description="No items found" class="h-48 justify-center" /> |
| 135 | </n-tab-pane> |
| 136 | <n-tab-pane name="Query stats" tab="Query stats" display-directive="show:lazy"> |
| 137 | <div class="p-6 pt-3"> |
| 138 | <template v-if="flow.query_stats.length"> |
| 139 | <AgentFlowQueryStat |
| 140 | v-for="stat of flow.query_stats" |
| 141 | :key="stat.first_active + stat.last_active" |
| 142 | :stat |
| 143 | embedded |
| 144 | class="item-appear item-appear-bottom item-appear-005 mb-2" |
| 145 | /> |
| 146 | </template> |
| 147 | <n-empty v-else description="No items found" class="h-48 justify-center" /> |
| 148 | </div> |
| 149 | </n-tab-pane> |
| 150 | <n-tab-pane name="Request" tab="Request" display-directive="show:lazy"> |
| 151 | <div class="p-6 pt-3"> |
| 152 | <SimpleJsonViewer |
| 153 | class="vuesjv-override" |
| 154 | :model-value="flow.request" |
| 155 | :initial-expanded-depth="2" |
| 156 | /> |
| 157 | </div> |
| 158 | </n-tab-pane> |
| 159 | <n-tab-pane name="Collect" tab="Collect" display-directive="show:lazy"> |
| 160 | <n-scrollbar class="max-h-106" trigger="none"> |
| 161 | <div class="px-7"> |
| 162 | <AgentFlowCollectList :flow /> |
| 163 | </div> |
| 164 | </n-scrollbar> |
| 165 | </n-tab-pane> |
| 166 | </n-tabs> |
| 167 | </n-modal> |
| 168 | </div> |
| 169 | </template> |
| 170 | |
| 171 | <script setup lang="ts"> |
| 172 | import type { FlowResult } from "@/types/flow.d" |
| 173 | import _pick from "lodash/pick" |
| 174 | import { NEmpty, NInput, NModal, NPopover, NScrollbar, NTabPane, NTabs } from "naive-ui" |
| 175 | import { computed, defineAsyncComponent, ref } from "vue" |
| 176 | import { SimpleJsonViewer } from "vue-sjv" |
| 177 | import Badge from "@/components/common/Badge.vue" |
| 178 | import CardEntity from "@/components/common/cards/CardEntity.vue" |
| 179 | import CardKV from "@/components/common/cards/CardKV.vue" |
| 180 | import Icon from "@/components/common/Icon.vue" |
| 181 | import { useSettingsStore } from "@/stores/settings" |
| 182 | import dayjs from "@/utils/dayjs" |
| 183 | import { formatDate } from "@/utils/format" |
| 184 | import "@/assets/scss/overrides/vuesjv-override.scss" |
| 185 | |
| 186 | const { flow, embedded } = defineProps<{ flow: FlowResult; embedded?: boolean }>() |
| 187 | const AgentFlowTimeline = defineAsyncComponent(() => import("./AgentFlowTimeline.vue")) |
| 188 | const AgentFlowQueryStat = defineAsyncComponent(() => import("./AgentFlowQueryStat.vue")) |
| 189 | const AgentFlowCollectList = defineAsyncComponent(() => import("./AgentFlowCollectList.vue")) |
| 190 | |
| 191 | const TimeIcon = "carbon:time" |
| 192 | const DisabledIcon = "carbon:subtract" |
| 193 | const EnabledIcon = "ri:check-line" |
| 194 | |
| 195 | const showDetails = ref(false) |
| 196 | const dFormats = useSettingsStore().dateFormat |
| 197 | |
| 198 | const executionDuration = computed(() => dayjs.duration(flow.execution_duration).humanize()) |
| 199 | |
| 200 | const properties = computed(() => { |
| 201 | return _pick(flow, [ |
| 202 | "client_id", |
| 203 | "next_response_id", |
| 204 | "outstanding_requests", |
| 205 | "total_collected_rows", |
| 206 | "total_expected_uploaded_bytes", |
| 207 | "total_loads", |
| 208 | "total_logs", |
| 209 | "total_requests", |
| 210 | "total_uploaded_bytes", |
| 211 | "total_uploaded_files", |
| 212 | "user_notified" |
| 213 | ]) |
| 214 | }) |
| 215 | </script> |