feat: Add generation time sort options to Media Asset Panel (#6698)

## Summary
Add generation time-based sorting options to the Media Asset Panel

## Changes
- **New sorting options**:
  - Generation time (longest first) - Sort by longest execution time
  - Generation time (fastest first) - Sort by shortest execution time

- **Show only in Generated tab**: 
- Generation time sorting is only meaningful for output assets with
`executionTimeInSeconds` metadata
  - Implemented conditional rendering via `showGenerationTimeSort` prop

## Technical Details
- `useMediaAssetFiltering.ts`: 
  - Added `'longest'` and `'fastest'` to `SortOption` type
  - Added `getAssetExecutionTime` helper function
  - Implemented sorting logic using switch-case pattern
  
- `MediaAssetSortMenu.vue`: 
  - Added `showGenerationTimeSort` prop
- Generation time sort buttons placed inside `<template
v-if="showGenerationTimeSort">`
  
- `MediaAssetFilterBar.vue`: 
- Receives `showGenerationTimeSort` prop and passes it to
`MediaAssetSortMenu`
  
- `AssetsSidebarTab.vue`: 
- Passes `showGenerationTimeSort` prop based on `activeTab === 'output'`
  
- `src/locales/en/main.json`: 
  - Added `sortLongestFirst`: "Generation time (longest first)"
  - Added `sortFastestFirst`: "Generation time (fastest first)"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Jin Yi
2025-11-14 16:09:32 +09:00
committed by GitHub
parent c43cd287bb
commit ad6dda4435
5 changed files with 75 additions and 20 deletions

View File

@@ -36,13 +36,14 @@
</div>
<!-- Normal Tab View -->
<TabList v-else v-model="activeTab" class="pt-4 pb-1">
<Tab value="input">{{ $t('sideToolbar.labels.imported') }}</Tab>
<Tab value="output">{{ $t('sideToolbar.labels.generated') }}</Tab>
<Tab value="input">{{ $t('sideToolbar.labels.imported') }}</Tab>
</TabList>
<!-- Filter Bar -->
<MediaAssetFilterBar
v-model:search-query="searchQuery"
v-model:sort-by="sortBy"
:show-generation-time-sort="activeTab === 'output'"
/>
</template>
<template #body>

View File

@@ -621,7 +621,9 @@
"mediaAssets": {
"title": "Media Assets",
"sortNewestFirst": "Newest first",
"sortOldestFirst": "Oldest first"
"sortOldestFirst": "Oldest first",
"sortLongestFirst": "Generation time (longest first)",
"sortFastestFirst": "Generation time (fastest first)"
},
"backToAssets": "Back to all assets",
"searchAssets": "Search assets...",

View File

@@ -14,6 +14,7 @@
<template #default="{ close }">
<MediaAssetSortMenu
:sort-by="sortBy"
:show-generation-time-sort
:close="close"
@update:sort-by="handleSortChange"
/>
@@ -29,23 +30,24 @@ import { isCloud } from '@/platform/distribution/types'
import AssetSortButton from './MediaAssetSortButton.vue'
import MediaAssetSortMenu from './MediaAssetSortMenu.vue'
interface MediaAssetSearchBarProps {
const { showGenerationTimeSort = false } = defineProps<{
searchQuery: string
sortBy: 'newest' | 'oldest'
}
defineProps<MediaAssetSearchBarProps>()
sortBy: 'newest' | 'oldest' | 'longest' | 'fastest'
showGenerationTimeSort?: boolean
}>()
const emit = defineEmits<{
'update:searchQuery': [value: string]
'update:sortBy': [value: 'newest' | 'oldest']
'update:sortBy': [value: 'newest' | 'oldest' | 'longest' | 'fastest']
}>()
const handleSearchChange = (value: string | undefined) => {
emit('update:searchQuery', value ?? '')
}
const handleSortChange = (value: 'newest' | 'oldest') => {
const handleSortChange = (
value: 'newest' | 'oldest' | 'longest' | 'fastest'
) => {
emit('update:sortBy', value)
}
</script>

View File

@@ -21,22 +21,53 @@
<i v-if="sortBy === 'oldest'" class="icon-[lucide--check] size-4" />
</template>
</IconTextButton>
<template v-if="showGenerationTimeSort">
<IconTextButton
type="transparent"
icon-position="right"
:label="$t('sideToolbar.mediaAssets.sortLongestFirst')"
@click="handleSortChange('longest')"
>
<template #icon>
<i v-if="sortBy === 'longest'" class="icon-[lucide--check] size-4" />
</template>
</IconTextButton>
<IconTextButton
type="transparent"
icon-position="right"
:label="$t('sideToolbar.mediaAssets.sortFastestFirst')"
@click="handleSortChange('fastest')"
>
<template #icon>
<i v-if="sortBy === 'fastest'" class="icon-[lucide--check] size-4" />
</template>
</IconTextButton>
</template>
</div>
</template>
<script setup lang="ts">
import IconTextButton from '@/components/button/IconTextButton.vue'
const { sortBy, close } = defineProps<{
sortBy: 'newest' | 'oldest'
const {
sortBy,
close,
showGenerationTimeSort = false
} = defineProps<{
sortBy: 'newest' | 'oldest' | 'longest' | 'fastest'
close: () => void
showGenerationTimeSort?: boolean
}>()
const emit = defineEmits<{
'update:sortBy': [value: 'newest' | 'oldest']
'update:sortBy': [value: 'newest' | 'oldest' | 'longest' | 'fastest']
}>()
const handleSortChange = (value: 'newest' | 'oldest') => {
const handleSortChange = (
value: 'newest' | 'oldest' | 'longest' | 'fastest'
) => {
emit('update:sortBy', value)
close()
}

View File

@@ -6,7 +6,7 @@ import type { Ref } from 'vue'
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
type SortOption = 'newest' | 'oldest'
type SortOption = 'newest' | 'oldest' | 'longest' | 'fastest'
/**
* Get timestamp from asset (either create_time or created_at)
@@ -18,6 +18,13 @@ const getAssetTime = (asset: AssetItem): number => {
)
}
/**
* Get execution time from asset user_metadata
*/
const getAssetExecutionTime = (asset: AssetItem): number => {
return (asset.user_metadata?.executionTimeInSeconds as number) ?? 0
}
/**
* Media Asset Filtering composable
* Manages search, filter, and sort for media assets
@@ -46,12 +53,24 @@ export function useMediaAssetFiltering(assets: Ref<AssetItem[]>) {
const filteredAssets = computed(() => {
// Sort by create_time (output assets) or created_at (input assets)
if (sortBy.value === 'oldest') {
// Ascending order (oldest first)
return sortByUtil(searchFiltered.value, [getAssetTime])
} else {
// Descending order (newest first) - negate for descending
return sortByUtil(searchFiltered.value, [(asset) => -getAssetTime(asset)])
switch (sortBy.value) {
case 'oldest':
// Ascending order (oldest first)
return sortByUtil(searchFiltered.value, [getAssetTime])
case 'longest':
// Descending order (longest execution time first)
return sortByUtil(searchFiltered.value, [
(asset) => -getAssetExecutionTime(asset)
])
case 'fastest':
// Ascending order (fastest execution time first)
return sortByUtil(searchFiltered.value, [getAssetExecutionTime])
case 'newest':
default:
// Descending order (newest first) - negate for descending
return sortByUtil(searchFiltered.value, [
(asset) => -getAssetTime(asset)
])
}
})