fix: support text and misc generated asset states (#8914)

## Summary
Align generated-asset state classification to a single shared source and
implement the missing text/misc states in both card and list previews.

## Changes
- **What**:
- Extended `getMediaTypeFromFilename` in
`packages/shared-frontend-utils` to return `text` and `other`, and
changed unknown/no-extension fallback from `image` to `other`.
- Added text extension handling (`txt`, `md`, `json`, `csv`, `yaml/yml`,
`xml`, `log`) and kept existing media kinds.
- Updated generated-assets UI to use shared media-type detection
directly (removed the local generated-assets classifier).
  - Added text and misc card preview components:
    - `text` -> `icon-[lucide--text]`
    - `other` -> `icon-[lucide--check-check]`
- Updated list-item preview behavior so only `image`/`video` use preview
media URLs; `text`/`other` use icon fallback.
- Widened media kind schema for asset display metadata to include `text`
and `other`.
- **Breaking**: No API breaking changes; internal media kind union
widened for frontend asset display paths.
- **Dependencies**: None.

## Review Focus
- Verify generated text assets render paragraph/text icon state in card
+ list.
- Verify unknown/misc assets consistently render double-check icon state
in card + list.
- Verify existing image/video/audio/3D behavior remains unchanged.

## Screenshots (if applicable)
<img width="282" height="158" alt="image"
src="https://github.com/user-attachments/assets/76cf2d1b-9d34-4c7c-92a1-50bbc55871e5"
/>
<img width="432" height="489" alt="image"
src="https://github.com/user-attachments/assets/024fece3-f241-484d-a37e-11948559ebbc"
/>
<img width="421" height="494" alt="image"
src="https://github.com/user-attachments/assets/ed64ba0c-bf46-4c3b-996e-4bc613ee029e"
/>


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8914-fix-support-text-and-misc-generated-asset-states-3096d73d365081f28ca7c32f306e4b50)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
Benjamin Lu
2026-02-21 01:27:28 -08:00
committed by GitHub
parent ea7bbb744f
commit 9184f9bce4
13 changed files with 209 additions and 26 deletions

View File

@@ -141,6 +141,40 @@ export const AudioAsset: Story = {
}
}
export const TextAsset: Story = {
decorators: [
() => ({
template: '<div style="max-width: 280px;"><story /></div>'
})
],
args: {
asset: {
...sampleAsset,
id: 'asset-5',
name: 'generation-notes.txt',
size: 2048,
preview_url: SAMPLE_MEDIA.image1
}
}
}
export const OtherAsset: Story = {
decorators: [
() => ({
template: '<div style="max-width: 280px;"><story /></div>'
})
],
args: {
asset: {
...sampleAsset,
id: 'asset-6',
name: 'workflow-payload.bin',
size: 8192,
preview_url: SAMPLE_MEDIA.image1
}
}
}
export const LoadingState: Story = {
decorators: [
() => ({

View File

@@ -36,7 +36,7 @@
<!-- Content based on asset type -->
<component
:is="getTopComponent(fileKind)"
:is="getTopComponent(previewKind)"
v-else-if="asset && adaptedAsset"
:asset="adaptedAsset"
:context="{ type: assetType }"
@@ -152,17 +152,21 @@ import type { MediaKind } from '../schemas/mediaAssetSchema'
import { MediaAssetKey } from '../schemas/mediaAssetSchema'
import MediaTitle from './MediaTitle.vue'
type PreviewKind = ReturnType<typeof getMediaTypeFromFilename>
const mediaComponents = {
top: {
video: defineAsyncComponent(() => import('./MediaVideoTop.vue')),
audio: defineAsyncComponent(() => import('./MediaAudioTop.vue')),
image: defineAsyncComponent(() => import('./MediaImageTop.vue')),
'3D': defineAsyncComponent(() => import('./Media3DTop.vue'))
'3D': defineAsyncComponent(() => import('./Media3DTop.vue')),
text: defineAsyncComponent(() => import('./MediaTextTop.vue')),
other: defineAsyncComponent(() => import('./MediaOtherTop.vue'))
}
}
function getTopComponent(kind: MediaKind) {
return mediaComponents.top[kind] || mediaComponents.top.image
function getTopComponent(kind: PreviewKind) {
return mediaComponents.top[kind] || mediaComponents.top.other
}
const { asset, loading, selected, showOutputCount, outputCount } = defineProps<{
@@ -206,7 +210,11 @@ const assetType = computed(() => {
// Determine file type from extension
const fileKind = computed((): MediaKind => {
return getMediaTypeFromFilename(asset?.name || '') as MediaKind
return getMediaTypeFromFilename(asset?.name || '')
})
const previewKind = computed((): PreviewKind => {
return getMediaTypeFromFilename(asset?.name || '')
})
// Get filename without extension

View File

@@ -0,0 +1,9 @@
<template>
<div class="relative size-full overflow-hidden rounded">
<div
class="flex size-full items-center justify-center bg-modal-card-placeholder-background transition-transform duration-300 group-hover:scale-105 group-data-[selected=true]:scale-105"
>
<i class="icon-[lucide--check-check] text-3xl text-base-foreground" />
</div>
</div>
</template>

View File

@@ -0,0 +1,9 @@
<template>
<div class="relative size-full overflow-hidden rounded">
<div
class="flex size-full items-center justify-center bg-modal-card-placeholder-background transition-transform duration-300 group-hover:scale-105 group-data-[selected=true]:scale-105"
>
<i class="icon-[lucide--text] text-3xl text-base-foreground" />
</div>
</div>
</template>

View File

@@ -3,7 +3,14 @@ import { z } from 'zod'
import { assetItemSchema } from './assetSchema'
const zMediaKindSchema = z.enum(['video', 'audio', 'image', '3D'])
const zMediaKindSchema = z.enum([
'video',
'audio',
'image',
'3D',
'text',
'other'
])
export type MediaKind = z.infer<typeof zMediaKindSchema>
const zDimensionsSchema = z.object({

View File

@@ -0,0 +1,17 @@
import { describe, expect, it } from 'vitest'
import { iconForMediaType } from './mediaIconUtil'
describe('iconForMediaType', () => {
it('maps text and misc fallbacks correctly', () => {
expect(iconForMediaType('text')).toBe('icon-[lucide--text]')
expect(iconForMediaType('other')).toBe('icon-[lucide--check-check]')
})
it('preserves existing mappings for core media types', () => {
expect(iconForMediaType('image')).toBe('icon-[lucide--image]')
expect(iconForMediaType('video')).toBe('icon-[lucide--video]')
expect(iconForMediaType('audio')).toBe('icon-[lucide--music]')
expect(iconForMediaType('3D')).toBe('icon-[lucide--box]')
})
})

View File

@@ -8,6 +8,10 @@ export function iconForMediaType(mediaType: MediaKind): string {
return 'icon-[lucide--music]'
case '3D':
return 'icon-[lucide--box]'
case 'text':
return 'icon-[lucide--text]'
case 'other':
return 'icon-[lucide--check-check]'
default:
return 'icon-[lucide--image]'
}