<template>
<details
ref="detailsEl"
class="content-card"
:class="contentTypeClass"
open
>
<summary>
<span class="content-icon">{{ icon }}</span>
<span class="content-title">{{ title }}</span>
<span class="content-badge">{{ contentType }}</span>
<i class="ph ph-caret-down content-chevron"></i>
</summary>
<div class="content-body">
<!-- STL -->
<iframe
v-if="contentType === 'stl'"
:src="viewerUrl('stl')"
class="content-iframe"
sandbox="allow-scripts"
loading="lazy"
/>
<!-- HTML -->
<iframe
v-else-if="contentType === 'html'"
:src="viewerUrl('html')"
class="content-iframe"
sandbox="allow-scripts allow-same-origin"
loading="lazy"
/>
<!-- SVG -->
<iframe
v-else-if="contentType === 'svg'"
:src="viewerUrl('svg')"
class="content-iframe"
sandbox=""
loading="lazy"
/>
<!-- PDF -->
<iframe
v-else-if="contentType === 'pdf'"
:src="viewerUrl('pdf')"
class="content-iframe"
loading="lazy"
/>
<!-- Image -->
<img
v-else-if="contentType === 'image'"
:src="url"
class="content-image"
:alt="title"
loading="lazy"
@click="openLightbox"
/>
<!-- Video -->
<video
v-else-if="contentType === 'video'"
:src="url"
class="content-video"
controls
playsinline
/>
<!-- Unknown -->
<div v-else class="content-unknown">
<p>Cannot preview this file type.</p>
<a :href="url" target="_blank" rel="noopener noreferrer" class="content-link">
Open in new tab
</a>
</div>
</div>
</details>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useLightbox } from '@/composables/useLightbox.js'
const props = defineProps({
tool: { type: Object, required: true }
})
const detailsEl = ref(null)
const { open } = useLightbox()
// content_publish tool stores metadata in the tool card
const metadata = computed(() => {
// metadata is set by onToolCall from WS event
if (props.tool.metadata) {
return props.tool.metadata
}
// Fallback: try to extract from output text
const match = props.tool.result?.match?.(/URL: (.+)/)
if (match) {
return { url: match[1].trim() }
}
return {}
})
const url = computed(() => metadata.value.url || '')
const contentType = computed(() => metadata.value.content_type || 'unknown')
const title = computed(() => metadata.value.title || metadata.value.filename || 'Content')
const CONTENT_ICONS = {
stl: '🧊',
html: '🌐',
svg: '🎨',
pdf: '📄',
image: '🖼️',
video: '🎬',
unknown: '📎'
}
const icon = computed(() => CONTENT_ICONS[contentType.value] || CONTENT_ICONS.unknown)
const contentTypeClass = computed(() => `is-${contentType.value}`)
function viewerUrl(viewerType) {
return `/content-viewers/${viewerType}.html?url=${encodeURIComponent(url.value)}`
}
function openLightbox() {
if (contentType.value === 'image') {
open(url.value)
}
}
</script>
<style scoped>
.content-card {
margin: 8px 0;
border-radius: 10px;
background: var(--surface, #1e1e2e);
border: 1px solid var(--border, #2a2a3e);
overflow: hidden;
}
.content-card summary {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 14px;
cursor: pointer;
user-select: none;
list-style: none;
}
.content-card summary::-webkit-details-marker {
display: none;
}
.content-icon {
font-size: 1.2em;
line-height: 1;
}
.content-title {
flex: 1;
font-size: 0.9em;
font-weight: 500;
color: var(--text, #cdd6f4);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.content-badge {
font-size: 0.7em;
text-transform: uppercase;
padding: 2px 8px;
border-radius: 4px;
background: var(--accent-muted, #313244);
color: var(--accent, #4ec9b0);
}
.content-chevron {
transition: transform 0.2s;
color: var(--text-muted, #6c7086);
}
.content-card[open] .content-chevron {
transform: rotate(180deg);
}
.content-body {
border-top: 1px solid var(--border, #2a2a3e);
}
.content-iframe {
width: 100%;
height: 300px;
border: none;
display: block;
}
@media (min-width: 768px) {
.content-iframe {
height: 500px;
}
}
.content-image {
width: 100%;
max-height: 60vh;
object-fit: contain;
cursor: zoom-in;
display: block;
background: var(--surface, #1e1e2e);
}
.content-video {
width: 100%;
max-height: 60vh;
display: block;
background: var(--surface, #1e1e2e);
}
.content-unknown {
padding: 20px;
text-align: center;
color: var(--text-muted, #6c7086);
}
.content-link {
color: var(--accent, #4ec9b0);
text-decoration: none;
}
.content-link:hover {
text-decoration: underline;
}
/* Type-specific accents */
.content-card.is-stl .content-badge { color: #f5c2e7; }
.content-card.is-html .content-badge { color: #89b4fa; }
.content-card.is-svg .content-badge { color: #a6e3a1; }
.content-card.is-pdf .content-badge { color: #fab387; }
.content-card.is-image .content-badge { color: #89dceb; }
.content-card.is-video .content-badge { color: #f38ba8; }
</style>