diff --git a/src/components/sidebar/tabs/AssetsSidebarListView.test.ts b/src/components/sidebar/tabs/AssetsSidebarListView.test.ts
index be6ead7e1..a99e23b62 100644
--- a/src/components/sidebar/tabs/AssetsSidebarListView.test.ts
+++ b/src/components/sidebar/tabs/AssetsSidebarListView.test.ts
@@ -2,8 +2,8 @@ import { mount } from '@vue/test-utils'
import { defineComponent } from 'vue'
import { describe, expect, it, vi } from 'vitest'
-import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
import type { OutputStackListItem } from '@/platform/assets/composables/useOutputStacks'
+import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
import AssetsSidebarListView from './AssetsSidebarListView.vue'
@@ -72,4 +72,21 @@ describe('AssetsSidebarListView', () => {
expect(wrapper.text()).not.toContain('sideToolbar.generatedAssetsHeader')
})
+
+ it('marks mp4 assets as video previews', () => {
+ const videoAsset = {
+ ...buildAsset('video-asset', 'clip.mp4'),
+ preview_url: '/api/view/clip.mp4',
+ user_metadata: {}
+ } satisfies AssetItem
+
+ const wrapper = mountListView([buildOutputItem(videoAsset)])
+
+ const listItems = wrapper.findAllComponents({ name: 'AssetsListItem' })
+ const assetListItem = listItems.at(-1)
+
+ expect(assetListItem).toBeDefined()
+ expect(assetListItem?.props('previewUrl')).toBe('/api/view/clip.mp4')
+ expect(assetListItem?.props('isVideoPreview')).toBe(true)
+ })
})
diff --git a/src/components/sidebar/tabs/AssetsSidebarListView.vue b/src/components/sidebar/tabs/AssetsSidebarListView.vue
index b1fc52ac5..0f06ff7ca 100644
--- a/src/components/sidebar/tabs/AssetsSidebarListView.vue
+++ b/src/components/sidebar/tabs/AssetsSidebarListView.vue
@@ -34,7 +34,7 @@
:aria-label="
t('assetBrowser.ariaLabel.assetCard', {
name: item.asset.name,
- type: getMediaTypeFromFilename(item.asset.name)
+ type: getAssetMediaType(item.asset)
})
"
:class="
@@ -45,9 +45,8 @@
"
:preview-url="item.asset.preview_url"
:preview-alt="item.asset.name"
- :icon-name="
- iconForMediaType(getMediaTypeFromFilename(item.asset.name))
- "
+ :icon-name="iconForMediaType(getAssetMediaType(item.asset))"
+ :is-video-preview="isVideoAsset(item.asset)"
:primary-text="getAssetPrimaryText(item.asset)"
:secondary-text="getAssetSecondaryText(item.asset)"
:stack-count="getStackCount(item.asset)"
@@ -135,6 +134,14 @@ function getAssetPrimaryText(asset: AssetItem): string {
return truncateFilename(asset.name)
}
+function getAssetMediaType(asset: AssetItem) {
+ return getMediaTypeFromFilename(asset.name)
+}
+
+function isVideoAsset(asset: AssetItem): boolean {
+ return getAssetMediaType(asset) === 'video'
+}
+
function getAssetSecondaryText(asset: AssetItem): string {
const metadata = getOutputAssetMetadata(asset.user_metadata)
if (typeof metadata?.executionTimeInSeconds === 'number') {
diff --git a/src/platform/assets/components/AssetsListItem.stories.ts b/src/platform/assets/components/AssetsListItem.stories.ts
index ebf66fcd7..91cd7bc63 100644
--- a/src/platform/assets/components/AssetsListItem.stories.ts
+++ b/src/platform/assets/components/AssetsListItem.stories.ts
@@ -82,6 +82,7 @@ export const GeneratedVideo: Story = {
args: {
previewUrl: VIDEO_PREVIEW,
previewAlt: 'clip-01.mp4',
+ isVideoPreview: true,
primaryText: 'clip-01.mp4',
secondaryText: '2m 12s'
}
diff --git a/src/platform/assets/components/AssetsListItem.test.ts b/src/platform/assets/components/AssetsListItem.test.ts
new file mode 100644
index 000000000..aab7edc98
--- /dev/null
+++ b/src/platform/assets/components/AssetsListItem.test.ts
@@ -0,0 +1,38 @@
+import { mount } from '@vue/test-utils'
+import { describe, expect, it } from 'vitest'
+
+import AssetsListItem from './AssetsListItem.vue'
+
+describe('AssetsListItem', () => {
+ it('renders video element with play overlay for video previews', () => {
+ const wrapper = mount(AssetsListItem, {
+ props: {
+ previewUrl: 'https://example.com/preview.mp4',
+ previewAlt: 'clip.mp4',
+ isVideoPreview: true
+ }
+ })
+
+ const video = wrapper.find('video')
+ expect(video.exists()).toBe(true)
+ expect(video.attributes('src')).toBe('https://example.com/preview.mp4')
+ expect(video.attributes('preload')).toBe('metadata')
+ expect(wrapper.find('img').exists()).toBe(false)
+ expect(wrapper.find('.bg-black\\/15').exists()).toBe(true)
+ expect(wrapper.find('.icon-\\[lucide--play\\]').exists()).toBe(true)
+ })
+
+ it('does not show play overlay for non-video previews', () => {
+ const wrapper = mount(AssetsListItem, {
+ props: {
+ previewUrl: 'https://example.com/preview.jpg',
+ previewAlt: 'image.png',
+ isVideoPreview: false
+ }
+ })
+
+ expect(wrapper.find('img').exists()).toBe(true)
+ expect(wrapper.find('video').exists()).toBe(false)
+ expect(wrapper.find('.icon-\\[lucide--play\\]').exists()).toBe(false)
+ })
+})
diff --git a/src/platform/assets/components/AssetsListItem.vue b/src/platform/assets/components/AssetsListItem.vue
index d20317147..581be8e60 100644
--- a/src/platform/assets/components/AssetsListItem.vue
+++ b/src/platform/assets/components/AssetsListItem.vue
@@ -35,12 +35,24 @@
:icon-class="iconClass"
:icon-aria-label="iconAriaLabel"
>
-
+