Files
agent-ui/frontend/src/components/transcript-debug/toolCards/ReadCard.vue
josedario87 4ab1d03370 feat: Add specialized tool cards for transcript-debug and glassmorphism bubbles
Add toolCards/ with rich visual cards for 10 tool types:
- AskUserQuestion, ExitPlanMode, EnterPlanMode
- Read, Write, Bash, Edit
- Grep, Glob
- Task/TaskCreate/TaskUpdate/TaskGet/TaskList (unified TaskCard)

Add MarkdownContent component and markdown/syntax highlight utils.
Make user/assistant bubbles transparent with backdrop blur.
2026-02-19 02:45:53 -06:00

170 lines
4.1 KiB
Vue

<script setup lang="ts">
import { ref, computed } from 'vue'
import type { ParsedToolCall } from '@/types/transcript-debug'
import ToolResultBlock from '../ToolResultBlock.vue'
const props = defineProps<{
call: ParsedToolCall
}>()
const filePath = computed(() => (props.call.input?.file_path as string) || '')
const fileName = computed(() => {
const fp = filePath.value
if (!fp) return ''
const parts = fp.replace(/\\/g, '/').split('/')
return parts[parts.length - 1]
})
const offset = computed(() => props.call.input?.offset as number | undefined)
const limit = computed(() => props.call.input?.limit as number | undefined)
const pages = computed(() => props.call.input?.pages as string | undefined)
const isError = computed(() => props.call.result?.isError ?? false)
// Detect file extension for icon hint
const ext = computed(() => {
const name = fileName.value
const dot = name.lastIndexOf('.')
return dot >= 0 ? name.slice(dot + 1).toLowerCase() : ''
})
const resultExpanded = ref(false)
</script>
<template>
<div :class="['read-card', { error: isError }]">
<div class="card-header">
<span class="card-icon">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
</svg>
</span>
<span class="card-label">Read</span>
<span v-if="ext" class="ext-badge">.{{ ext }}</span>
<span v-if="isError" class="error-badge">error</span>
</div>
<div class="card-body">
<div class="file-path" :title="filePath">
<span class="path-dir">{{ filePath.replace(/\\/g, '/').split('/').slice(0, -1).join('/') }}/</span>
<span class="path-file">{{ fileName }}</span>
</div>
<div v-if="offset != null || limit != null || pages" class="range-info">
<span v-if="offset != null" class="range-badge">offset: {{ offset }}</span>
<span v-if="limit != null" class="range-badge">limit: {{ limit }}</span>
<span v-if="pages" class="range-badge">pages: {{ pages }}</span>
</div>
</div>
<!-- Result -->
<ToolResultBlock v-if="call.result" :result="call.result" />
</div>
</template>
<style scoped>
.read-card {
border: 1px solid rgba(6, 182, 212, 0.25);
border-left: 3px solid #06b6d4;
border-radius: 8px;
overflow: hidden;
margin: 0.5rem 0;
background: var(--bg-primary);
}
.read-card.error {
border-color: rgba(239, 68, 68, 0.25);
border-left-color: #ef4444;
}
.card-header {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.45rem 0.75rem;
background: rgba(6, 182, 212, 0.06);
border-bottom: 1px solid rgba(6, 182, 212, 0.12);
}
.read-card.error .card-header {
background: rgba(239, 68, 68, 0.06);
border-bottom-color: rgba(239, 68, 68, 0.12);
}
.card-icon {
display: flex;
align-items: center;
color: #06b6d4;
}
.read-card.error .card-icon { color: #ef4444; }
.card-label {
font-size: 11px;
font-weight: 600;
color: #06b6d4;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.read-card.error .card-label { color: #ef4444; }
.ext-badge {
font-size: 10px;
padding: 0.1rem 0.35rem;
border-radius: 4px;
background: rgba(6, 182, 212, 0.12);
color: #06b6d4;
font-family: 'SF Mono', 'Fira Code', monospace;
font-weight: 500;
}
.error-badge {
font-size: 10px;
padding: 0.1rem 0.4rem;
border-radius: 4px;
background: rgba(239, 68, 68, 0.15);
color: #ef4444;
font-weight: 500;
}
.card-body {
padding: 0.5rem 0.75rem;
display: flex;
flex-direction: column;
gap: 0.3rem;
}
.file-path {
font-size: 12px;
font-family: 'SF Mono', 'Fira Code', monospace;
line-height: 1.4;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.path-dir {
color: var(--text-muted);
}
.path-file {
color: var(--text-primary);
font-weight: 600;
}
.range-info {
display: flex;
gap: 0.4rem;
flex-wrap: wrap;
}
.range-badge {
font-size: 10px;
padding: 0.1rem 0.35rem;
border-radius: 4px;
background: var(--bg-secondary);
color: var(--text-muted);
font-family: 'SF Mono', 'Fira Code', monospace;
}
</style>