feat(chat): enhance chat functionality with song integration and UI improvements
- Updated ChatHeader and ChatMessage components to support song selection and display. - Enhanced useContentPanel to handle both film and song content, allowing for richer interactions. - Improved FilmCard and added SongCard components for better media representation. - Refactored environment variables for better configuration management. - Updated .gitignore to include development chat history. Made-with: Cursor
This commit is contained in:
@@ -13,44 +13,66 @@
|
||||
<!-- Content surface (main area) -->
|
||||
<main
|
||||
class="flex-1 min-w-0 glass-card overflow-hidden flex flex-col"
|
||||
:class="panelSide === 'left' ? 'order-last' : 'order-first'"
|
||||
:class="[
|
||||
panelSide === 'left' ? 'order-last' : 'order-first',
|
||||
(selectedFilm || selectedSong) && 'detail-active'
|
||||
]"
|
||||
>
|
||||
<div v-if="panelOpen" class="flex-1 min-h-0">
|
||||
<FilmDetail
|
||||
v-if="selectedFilm"
|
||||
:film="selectedFilm"
|
||||
@back="closeFilmDetail"
|
||||
/>
|
||||
<FilmGrid
|
||||
v-else
|
||||
:films="panelFilms"
|
||||
:title="panelTitle"
|
||||
@select-film="openFilmDetail"
|
||||
/>
|
||||
</div>
|
||||
<Transition name="content-fade" mode="out-in">
|
||||
<div v-if="panelOpen" key="content" class="flex-1 min-h-0 flex flex-col">
|
||||
<FilmDetail
|
||||
v-if="selectedFilm"
|
||||
:film="selectedFilm"
|
||||
@back="closeFilmDetail"
|
||||
/>
|
||||
<SongDetail
|
||||
v-else-if="selectedSong"
|
||||
:song="selectedSong"
|
||||
@back="closeSongDetail"
|
||||
/>
|
||||
<FilmGrid
|
||||
v-else-if="contentType === 'film'"
|
||||
:films="panelFilms"
|
||||
:title="panelTitle"
|
||||
@select-film="openFilmDetail"
|
||||
/>
|
||||
<SongGrid
|
||||
v-else
|
||||
:songs="panelSongs"
|
||||
:title="panelTitle"
|
||||
@select-song="openSongDetail"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-else 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 glass flex items-center justify-center mx-auto">
|
||||
<span class="text-3xl">✦</span>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-lg font-bold mb-1"
|
||||
:class="isDark ? 'text-white/80' : 'text-gray-800'">
|
||||
Content Surface
|
||||
</h2>
|
||||
<p class="text-sm leading-relaxed"
|
||||
:class="isDark ? 'text-white/30' : 'text-gray-400'">
|
||||
Ask about films in the chat to see rich content here.
|
||||
</p>
|
||||
<ContextLoader
|
||||
v-else-if="chatStore.isStreaming"
|
||||
key="loader"
|
||||
:context-type="loaderContextType"
|
||||
/>
|
||||
|
||||
<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 glass flex items-center justify-center mx-auto">
|
||||
<span class="text-3xl" :class="isDark ? 'text-[#fafafa]' : 'text-gray-800'">✦</span>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-lg font-bold mb-1"
|
||||
:class="isDark ? 'text-white/80' : 'text-gray-800'">
|
||||
Content Surface
|
||||
</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.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</main>
|
||||
|
||||
<!-- Chat panel -->
|
||||
<aside
|
||||
class="w-80 xl:w-96 shrink-0 flex flex-col glass-card overflow-hidden"
|
||||
class="w-80 xl:w-96 shrink-0 flex flex-col glass-card overflow-visible"
|
||||
:class="panelSide === 'left' ? 'order-first' : 'order-last'"
|
||||
>
|
||||
<ChatWindow
|
||||
@@ -72,6 +94,9 @@ import { useContentPanel } from '@/composables/useContentPanel'
|
||||
import ChatWindow from '@/components/chat/ChatWindow.vue'
|
||||
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 ContextLoader from '@/components/content/ContextLoader.vue'
|
||||
|
||||
const chatStore = useChatStore()
|
||||
const { isDark } = useTheme()
|
||||
@@ -81,11 +106,46 @@ useAI()
|
||||
const {
|
||||
panelOpen,
|
||||
panelFilms,
|
||||
panelSongs,
|
||||
panelTitle,
|
||||
contentType,
|
||||
selectedFilm,
|
||||
selectedSong,
|
||||
openFilmDetail,
|
||||
closeFilmDetail,
|
||||
openSongDetail,
|
||||
closeSongDetail,
|
||||
} = useContentPanel()
|
||||
|
||||
const panelSide = computed(() => chatStore.panelSide)
|
||||
|
||||
// Infer loader type from last user message when streaming (before tags are parsed)
|
||||
const loaderContextType = computed(() => {
|
||||
if (panelOpen.value) return contentType.value
|
||||
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'
|
||||
return contentType.value
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.content-fade-enter-active,
|
||||
.content-fade-leave-active {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
.content-fade-enter-from,
|
||||
.content-fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* When film detail is active, hide the border so the hero image
|
||||
* fills edge-to-edge with no visible seam lines.
|
||||
* The card keeps its border-radius and overflow:hidden,
|
||||
* so the image is still clipped to rounded corners.
|
||||
*/
|
||||
.detail-active {
|
||||
border-color: transparent !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user