|
| 1 | +<script setup lang="ts"> |
| 2 | +import { computed } from 'vue' |
| 3 | +import AButton from 'ant-design-vue/es/button' |
| 4 | +import ADrawer from 'ant-design-vue/es/drawer' |
| 5 | +import ATag from 'ant-design-vue/es/tag' |
| 6 | +import Typography from 'ant-design-vue/es/typography' |
| 7 | +import { ArrowRightOutlined } from '@ant-design/icons-vue' |
| 8 | +import { formatDateTime, formatDuration, formatNumber, shortPath, type ToolCall } from '../api' |
| 9 | +
|
| 10 | +const ATypographyParagraph = Typography.Paragraph |
| 11 | +const ATypographyText = Typography.Text |
| 12 | +
|
| 13 | +const props = withDefaults( |
| 14 | + defineProps<{ |
| 15 | + open: boolean |
| 16 | + call: ToolCall | null |
| 17 | + showSessionLink?: boolean |
| 18 | + }>(), |
| 19 | + { showSessionLink: true } |
| 20 | +) |
| 21 | +
|
| 22 | +const emit = defineEmits<{ |
| 23 | + close: [] |
| 24 | + openSession: [id: number] |
| 25 | +}>() |
| 26 | +
|
| 27 | +const drawerTitle = computed(() => { |
| 28 | + if (!props.call?.toolName) return 'Tool Call Details' |
| 29 | + return `${props.call.toolName} Details` |
| 30 | +}) |
| 31 | +
|
| 32 | +function normalizedStatus(status?: string) { |
| 33 | + return (status || 'unknown').toLowerCase() |
| 34 | +} |
| 35 | +
|
| 36 | +function statusClass(status?: string) { |
| 37 | + const normalized = normalizedStatus(status) |
| 38 | + if (['completed', 'ok', 'indexed', 'success'].includes(normalized)) return 'status-ok' |
| 39 | + if (['pending', 'warning', 'scanning', 'unknown', 'started'].includes(normalized)) return 'status-warning' |
| 40 | + return 'status-error' |
| 41 | +} |
| 42 | +
|
| 43 | +function statusColor(status?: string) { |
| 44 | + const normalized = normalizedStatus(status) |
| 45 | + if (['completed', 'ok', 'indexed', 'success'].includes(normalized)) return 'success' |
| 46 | + if (normalized === 'scanning') return 'processing' |
| 47 | + if (['pending', 'warning', 'unknown', 'started'].includes(normalized)) return 'warning' |
| 48 | + return 'error' |
| 49 | +} |
| 50 | +
|
| 51 | +function sessionName(call: ToolCall) { |
| 52 | + return call.sessionKey || call.codexSessionId || `#${call.sessionId}` |
| 53 | +} |
| 54 | +
|
| 55 | +function hasText(value?: string) { |
| 56 | + return Boolean(value && value.trim()) |
| 57 | +} |
| 58 | +
|
| 59 | +function formatLine(value?: number) { |
| 60 | + return value ? formatNumber(value) : '-' |
| 61 | +} |
| 62 | +
|
| 63 | +function openSession(call: ToolCall) { |
| 64 | + emit('openSession', call.sessionId) |
| 65 | +} |
| 66 | +
|
| 67 | +function hasDistinctEndRaw(call: ToolCall) { |
| 68 | + return hasText(call.rawEndEventJson) && call.rawEndEventJson !== call.rawStartEventJson |
| 69 | +} |
| 70 | +</script> |
| 71 | + |
| 72 | +<template> |
| 73 | + <a-drawer class="tool-call-drawer" :open="props.open" :width="720" placement="right" @close="emit('close')"> |
| 74 | + <template #title>{{ drawerTitle }}</template> |
| 75 | + |
| 76 | + <template v-if="props.call"> |
| 77 | + <div class="tool-detail-summary"> |
| 78 | + <div class="tool-detail-heading"> |
| 79 | + <div class="metric-label">Tool</div> |
| 80 | + <div class="summary-title">{{ props.call.toolName || 'unknown' }}</div> |
| 81 | + <div class="summary-meta"> |
| 82 | + <a-tag class="status-tag call-status-tag" :class="statusClass(props.call.status)" :color="statusColor(props.call.status)"> |
| 83 | + {{ props.call.status || 'unknown' }} |
| 84 | + </a-tag> |
| 85 | + <span class="summary-chip mono">#{{ formatNumber(props.call.id) }}</span> |
| 86 | + <span v-if="props.call.callId" class="summary-chip mono">{{ props.call.callId }}</span> |
| 87 | + </div> |
| 88 | + </div> |
| 89 | + <a-button v-if="props.showSessionLink && props.call.sessionId" @click="openSession(props.call)"> |
| 90 | + <template #icon> |
| 91 | + <ArrowRightOutlined /> |
| 92 | + </template> |
| 93 | + Session |
| 94 | + </a-button> |
| 95 | + </div> |
| 96 | + |
| 97 | + <div class="metadata-grid tool-detail-grid"> |
| 98 | + <div class="metadata-item"> |
| 99 | + <div class="metadata-label">Started</div> |
| 100 | + <div class="metadata-value">{{ formatDateTime(props.call.startedAt) }}</div> |
| 101 | + </div> |
| 102 | + <div class="metadata-item"> |
| 103 | + <div class="metadata-label">Ended</div> |
| 104 | + <div class="metadata-value">{{ formatDateTime(props.call.endedAt) }}</div> |
| 105 | + </div> |
| 106 | + <div class="metadata-item"> |
| 107 | + <div class="metadata-label">Duration</div> |
| 108 | + <div class="metadata-value number-cell">{{ formatDuration(props.call.durationMs) }}</div> |
| 109 | + </div> |
| 110 | + <div class="metadata-item"> |
| 111 | + <div class="metadata-label">Session</div> |
| 112 | + <div class="metadata-value mono">{{ sessionName(props.call) }}</div> |
| 113 | + </div> |
| 114 | + <div class="metadata-item"> |
| 115 | + <div class="metadata-label">Agent</div> |
| 116 | + <div class="metadata-value">{{ props.call.agentName || props.call.agentKind || '-' }}</div> |
| 117 | + </div> |
| 118 | + <div class="metadata-item"> |
| 119 | + <div class="metadata-label">Raw Events</div> |
| 120 | + <div class="metadata-value mono"> |
| 121 | + {{ formatLine(props.call.rawStartEventLine || props.call.rawEventLine) }} -> {{ formatLine(props.call.rawEndEventLine) }} |
| 122 | + </div> |
| 123 | + </div> |
| 124 | + <div class="metadata-item is-wide"> |
| 125 | + <div class="metadata-label">Project</div> |
| 126 | + <a-typography-text class="metadata-value detail-path" :ellipsis="{ tooltip: props.call.projectPath }"> |
| 127 | + {{ props.call.projectPath || '-' }} |
| 128 | + </a-typography-text> |
| 129 | + </div> |
| 130 | + <div class="metadata-item is-wide"> |
| 131 | + <div class="metadata-label">Raw Source</div> |
| 132 | + <a-typography-text class="metadata-value detail-path mono" :ellipsis="{ tooltip: props.call.rawSourcePath }"> |
| 133 | + {{ props.call.rawSourcePath ? shortPath(props.call.rawSourcePath) : '-' }} |
| 134 | + </a-typography-text> |
| 135 | + </div> |
| 136 | + </div> |
| 137 | + |
| 138 | + <section class="detail-section"> |
| 139 | + <div class="metadata-label">Input</div> |
| 140 | + <a-typography-paragraph class="detail-pre mono" copyable> |
| 141 | + {{ props.call.inputSummary || '-' }} |
| 142 | + </a-typography-paragraph> |
| 143 | + </section> |
| 144 | + |
| 145 | + <section class="detail-section"> |
| 146 | + <div class="metadata-label">Output</div> |
| 147 | + <a-typography-paragraph class="detail-pre mono" copyable> |
| 148 | + {{ props.call.outputSummary || '-' }} |
| 149 | + </a-typography-paragraph> |
| 150 | + </section> |
| 151 | + |
| 152 | + <section v-if="props.call.error" class="detail-section"> |
| 153 | + <div class="metadata-label">Error</div> |
| 154 | + <a-typography-paragraph class="detail-pre detail-pre-error mono" copyable> |
| 155 | + {{ props.call.error }} |
| 156 | + </a-typography-paragraph> |
| 157 | + </section> |
| 158 | + |
| 159 | + <details v-if="hasText(props.call.rawStartEventJson)" class="raw-detail" open> |
| 160 | + <summary> |
| 161 | + Start raw event |
| 162 | + <span class="muted mono">line {{ formatLine(props.call.rawStartEventLine || props.call.rawEventLine) }} · {{ props.call.rawStartEventType || '-' }}</span> |
| 163 | + </summary> |
| 164 | + <div v-if="props.call.rawStartEventSummary" class="raw-detail-summary">{{ props.call.rawStartEventSummary }}</div> |
| 165 | + <a-typography-paragraph class="detail-pre raw-json mono" copyable> |
| 166 | + {{ props.call.rawStartEventJson }} |
| 167 | + </a-typography-paragraph> |
| 168 | + </details> |
| 169 | + |
| 170 | + <details v-if="hasDistinctEndRaw(props.call)" class="raw-detail"> |
| 171 | + <summary> |
| 172 | + End raw event |
| 173 | + <span class="muted mono">line {{ formatLine(props.call.rawEndEventLine) }} · {{ props.call.rawEndEventType || '-' }}</span> |
| 174 | + </summary> |
| 175 | + <div v-if="props.call.rawEndEventSummary" class="raw-detail-summary">{{ props.call.rawEndEventSummary }}</div> |
| 176 | + <a-typography-paragraph class="detail-pre raw-json mono" copyable> |
| 177 | + {{ props.call.rawEndEventJson }} |
| 178 | + </a-typography-paragraph> |
| 179 | + </details> |
| 180 | + |
| 181 | + <div v-if="!hasText(props.call.rawStartEventJson) && !hasText(props.call.rawEndEventJson)" class="metadata-item"> |
| 182 | + <div class="metadata-label">Raw Event</div> |
| 183 | + <div class="metadata-value">No raw event recorded</div> |
| 184 | + </div> |
| 185 | + </template> |
| 186 | + </a-drawer> |
| 187 | +</template> |
0 commit comments