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:
Dorian
2026-03-02 18:16:04 +00:00
parent 6f79f0860d
commit 80aeefa4bf
27 changed files with 1751 additions and 143 deletions
+90 -30
View File
@@ -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>