feat(chat): integrate podcast support into chat components
- Updated ChatMessage and ChatWindow components to handle podcast content alongside films and songs. - Enhanced useContentPanel to extract and manage podcasts, allowing for richer media interactions. - Added PodcastCard and PodcastGrid components for displaying podcast information. - Improved UI elements to accommodate podcast selection and detail viewing. - Updated styles for empty state icons and added new CSS for podcast-related elements. Made-with: Cursor
This commit is contained in:
@@ -14,12 +14,26 @@
|
||||
|
||||
<!-- Content surface (main area) -->
|
||||
<main
|
||||
class="flex-1 min-w-0 path-glass-card overflow-hidden flex flex-col"
|
||||
class="flex-1 min-w-0 path-glass-card overflow-hidden flex flex-col relative"
|
||||
:class="[
|
||||
panelSide === 'left' ? 'order-last' : 'order-first',
|
||||
(selectedFilm || selectedSong) && 'detail-active'
|
||||
(selectedFilm || selectedSong || selectedPodcast) && 'detail-active'
|
||||
]"
|
||||
>
|
||||
<button
|
||||
v-if="(selectedFilm || selectedSong || selectedPodcast) && (panelOpen || chatStore.isStreaming)"
|
||||
class="absolute top-3 right-3 z-10 p-2 rounded-lg transition-colors"
|
||||
:class="isDark
|
||||
? 'text-white/70 hover:bg-white/10'
|
||||
: 'text-gray-500 hover:bg-black/5 hover:text-gray-800'"
|
||||
title="Clear content"
|
||||
aria-label="Clear content"
|
||||
@click="closePanel"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
<Transition name="content-fade" mode="out-in">
|
||||
<div v-if="panelOpen" key="content" class="flex-1 min-h-0 flex flex-col">
|
||||
<FilmDetail
|
||||
@@ -32,30 +46,105 @@
|
||||
:song="selectedSong"
|
||||
@back="closeSongDetail"
|
||||
/>
|
||||
<PodcastDetail
|
||||
v-else-if="selectedPodcast"
|
||||
:podcast="selectedPodcast"
|
||||
@back="closePodcastDetail"
|
||||
/>
|
||||
<FilmGrid
|
||||
v-else-if="contentType === 'film'"
|
||||
:films="panelFilms"
|
||||
:title="panelTitle"
|
||||
@select-film="openFilmDetail"
|
||||
/>
|
||||
>
|
||||
<template #header-actions>
|
||||
<button
|
||||
class="p-2 rounded-lg transition-colors -mr-1"
|
||||
:class="isDark
|
||||
? 'text-white/70 hover:bg-white/10'
|
||||
: 'text-gray-500 hover:bg-black/5 hover:text-gray-800'"
|
||||
title="Clear content"
|
||||
aria-label="Clear content"
|
||||
@click="closePanel"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</template>
|
||||
</FilmGrid>
|
||||
<SongGrid
|
||||
v-else
|
||||
v-else-if="contentType === 'song'"
|
||||
:songs="panelSongs"
|
||||
:title="panelTitle"
|
||||
@select-song="openSongDetail"
|
||||
/>
|
||||
>
|
||||
<template #header-actions>
|
||||
<button
|
||||
class="p-2 rounded-lg transition-colors -mr-1"
|
||||
:class="isDark
|
||||
? 'text-white/70 hover:bg-white/10'
|
||||
: 'text-gray-500 hover:bg-black/5 hover:text-gray-800'"
|
||||
title="Clear content"
|
||||
aria-label="Clear content"
|
||||
@click="closePanel"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</template>
|
||||
</SongGrid>
|
||||
<PodcastGrid
|
||||
v-else
|
||||
:podcasts="panelPodcasts"
|
||||
:title="panelTitle"
|
||||
@select-podcast="openPodcastDetail"
|
||||
>
|
||||
<template #header-actions>
|
||||
<button
|
||||
class="p-2 rounded-lg transition-colors -mr-1"
|
||||
:class="isDark
|
||||
? 'text-white/70 hover:bg-white/10'
|
||||
: 'text-gray-500 hover:bg-black/5 hover:text-gray-800'"
|
||||
title="Clear content"
|
||||
aria-label="Clear content"
|
||||
@click="closePanel"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</template>
|
||||
</PodcastGrid>
|
||||
</div>
|
||||
|
||||
<ContextLoader
|
||||
v-else-if="chatStore.isStreaming"
|
||||
key="loader"
|
||||
:context-type="loaderContextType"
|
||||
/>
|
||||
>
|
||||
<template #header-actions>
|
||||
<button
|
||||
class="p-2 rounded-lg transition-colors -mr-1"
|
||||
:class="isDark
|
||||
? 'text-white/70 hover:bg-white/10'
|
||||
: 'text-gray-500 hover:bg-black/5 hover:text-gray-800'"
|
||||
title="Clear content"
|
||||
aria-label="Clear content"
|
||||
@click="closePanel"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</template>
|
||||
</ContextLoader>
|
||||
|
||||
<div v-else key="empty" class="flex-1 flex items-center justify-center">
|
||||
<div class="text-center space-y-4 animate-fade-up max-w-sm px-6">
|
||||
<div class="w-20 h-20 rounded-2xl path-glass-card flex items-center justify-center mx-auto overflow-hidden">
|
||||
<img src="/icon.svg" alt="AIUI" class="w-12 h-12 object-contain" />
|
||||
<div class="empty-state-icon w-20 h-20 rounded-2xl path-glass-icon flex items-center justify-center mx-auto overflow-hidden">
|
||||
<span class="text-3xl" :class="isDark ? 'text-[#fafafa]' : 'text-gray-800'">✦</span>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-lg font-bold mb-1"
|
||||
@@ -64,7 +153,7 @@
|
||||
</h2>
|
||||
<p class="text-sm leading-relaxed"
|
||||
:class="isDark ? 'text-white/30' : 'text-gray-400'">
|
||||
Ask about films or songs in the chat to see rich content here.
|
||||
Ask about films, songs, or podcasts in the chat to see rich content here.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -100,6 +189,8 @@ import FilmGrid from '@/components/content/FilmGrid.vue'
|
||||
import FilmDetail from '@/components/content/FilmDetail.vue'
|
||||
import SongGrid from '@/components/content/SongGrid.vue'
|
||||
import SongDetail from '@/components/content/SongDetail.vue'
|
||||
import PodcastGrid from '@/components/content/PodcastGrid.vue'
|
||||
import PodcastDetail from '@/components/content/PodcastDetail.vue'
|
||||
import ContextLoader from '@/components/content/ContextLoader.vue'
|
||||
import PlayerBar from '@/components/player/PlayerBar.vue'
|
||||
import { usePlayer } from '@/composables/usePlayer'
|
||||
@@ -114,14 +205,19 @@ const {
|
||||
panelOpen,
|
||||
panelFilms,
|
||||
panelSongs,
|
||||
panelPodcasts,
|
||||
panelTitle,
|
||||
contentType,
|
||||
selectedFilm,
|
||||
selectedSong,
|
||||
selectedPodcast,
|
||||
openFilmDetail,
|
||||
closeFilmDetail,
|
||||
openSongDetail,
|
||||
closeSongDetail,
|
||||
openPodcastDetail,
|
||||
closePodcastDetail,
|
||||
closePanel,
|
||||
} = useContentPanel()
|
||||
|
||||
const panelSide = computed(() => chatStore.panelSide)
|
||||
@@ -132,6 +228,7 @@ const loaderContextType = computed(() => {
|
||||
const lastUser = [...chatStore.messages].reverse().find((m) => m.role === 'user')
|
||||
const q = (lastUser?.content ?? '').toLowerCase()
|
||||
if (/\b(song|music|track|album|band|artist|listen)\b/.test(q)) return 'song'
|
||||
if (/\b(podcast|episode|show|listen to)\b/.test(q)) return 'podcast'
|
||||
return contentType.value
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user