feat(chat): enhance chat functionality with web search and article integration
- Updated ChatMessage and ChatWindow components to support inline web search results and articles. - Integrated new web search and RSS plugins into the chat system for real-time information retrieval. - Enhanced useContentPanel to manage web search results alongside existing media types. - Added ArticleOverlay component for displaying selected articles from search results. - Improved UI elements and styles for better user interaction with web search features. Made-with: Cursor
This commit is contained in:
@@ -17,12 +17,12 @@
|
||||
class="flex-1 min-w-0 path-glass-card overflow-hidden flex flex-col relative"
|
||||
:class="[
|
||||
panelSide === 'left' ? 'order-last' : 'order-first',
|
||||
(selectedFilm || selectedSong || selectedPodcast) && 'detail-active'
|
||||
(selectedFilm || selectedSong || selectedPodcast || selectedArticle) && '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"
|
||||
v-if="(selectedFilm || selectedSong || selectedPodcast || selectedArticle) && (panelOpen || chatStore.isStreaming)"
|
||||
class="absolute top-3 right-3 z-10 p-2 rounded-lg path-glass-icon transition-colors"
|
||||
:class="isDark
|
||||
? 'text-white/70 hover:bg-white/10'
|
||||
: 'text-gray-500 hover:bg-black/5 hover:text-gray-800'"
|
||||
@@ -51,15 +51,44 @@
|
||||
:podcast="selectedPodcast"
|
||||
@back="closePodcastDetail"
|
||||
/>
|
||||
<FilmGrid
|
||||
v-else-if="contentType === 'film'"
|
||||
:films="panelFilms"
|
||||
:title="panelTitle"
|
||||
@select-film="openFilmDetail"
|
||||
>
|
||||
<ArticleDetail
|
||||
v-else-if="selectedArticle"
|
||||
:article="selectedArticle"
|
||||
@back="closeArticleDetail"
|
||||
/>
|
||||
<template v-else>
|
||||
<div
|
||||
v-if="availableTabs.length > 1"
|
||||
class="shrink-0 flex items-center justify-between gap-2 px-4 py-2"
|
||||
:style="isDark
|
||||
? 'border-bottom: 1px solid rgba(255, 255, 255, 0.08)'
|
||||
: 'border-bottom: 1px solid rgba(0, 0, 0, 0.06)'"
|
||||
>
|
||||
<div class="flex flex-wrap gap-1.5 flex-1 min-w-0 justify-center">
|
||||
<button
|
||||
v-for="tab in availableTabs"
|
||||
:key="tab"
|
||||
class="text-[10px] px-2 py-1 rounded-md transition-all duration-150"
|
||||
:class="activeTab === tab
|
||||
? 'nav-tab-active'
|
||||
: isDark
|
||||
? 'text-white/40 hover:text-white/70 hover:bg-white/5'
|
||||
: 'text-gray-500 hover:text-gray-800 hover:bg-black/5'"
|
||||
@click="setActiveTab(tab)"
|
||||
>
|
||||
{{ tab === 'film' ? 'Films' : tab === 'song' ? 'Songs' : tab === 'podcast' ? 'Podcasts' : tab === 'magazine' ? 'Magazine' : tab === 'news' ? 'News' : 'Websites' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<FilmGrid
|
||||
v-if="activeTab === 'film'"
|
||||
:films="panelFilms"
|
||||
:title="panelTitle"
|
||||
@select-film="openFilmDetail"
|
||||
>
|
||||
<template #header-actions>
|
||||
<button
|
||||
class="p-2 rounded-lg transition-colors -mr-1"
|
||||
class="flex items-center justify-center 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'"
|
||||
@@ -67,21 +96,21 @@
|
||||
aria-label="Clear content"
|
||||
@click="closePanel"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-4 h-4 shrink-0" 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-if="contentType === 'song'"
|
||||
v-else-if="activeTab === 'song'"
|
||||
:songs="panelSongs"
|
||||
:title="panelTitle"
|
||||
@select-song="openSongDetail"
|
||||
>
|
||||
<template #header-actions>
|
||||
<button
|
||||
class="p-2 rounded-lg transition-colors -mr-1"
|
||||
class="flex items-center justify-center 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'"
|
||||
@@ -89,21 +118,88 @@
|
||||
aria-label="Clear content"
|
||||
@click="closePanel"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-4 h-4 shrink-0" 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>
|
||||
<MagazineGrid
|
||||
v-else-if="activeTab === 'magazine'"
|
||||
:sections="panelMagazineSections"
|
||||
:hero-image-url="panelMagazineHeroImage"
|
||||
:title="panelTitle"
|
||||
:query="panelQuery"
|
||||
>
|
||||
<template #header-actions>
|
||||
<button
|
||||
class="flex items-center justify-center 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 shrink-0" 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>
|
||||
</MagazineGrid>
|
||||
<NewsGrid
|
||||
v-else-if="activeTab === 'news'"
|
||||
:articles="panelWebResults"
|
||||
:title="panelTitle"
|
||||
:query="panelQuery"
|
||||
>
|
||||
<template #header-actions>
|
||||
<button
|
||||
class="flex items-center justify-center 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 shrink-0" 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>
|
||||
</NewsGrid>
|
||||
<NewsGrid
|
||||
v-else-if="activeTab === 'websites'"
|
||||
:articles="panelWebsites"
|
||||
:title="panelTitle"
|
||||
variant="websites"
|
||||
>
|
||||
<template #header-actions>
|
||||
<button
|
||||
class="flex items-center justify-center 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 shrink-0" 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>
|
||||
</NewsGrid>
|
||||
<PodcastGrid
|
||||
v-else
|
||||
v-else-if="activeTab === 'podcast'"
|
||||
:podcasts="panelPodcasts"
|
||||
:title="panelTitle"
|
||||
@select-podcast="openPodcastDetail"
|
||||
>
|
||||
<template #header-actions>
|
||||
<button
|
||||
class="p-2 rounded-lg transition-colors -mr-1"
|
||||
class="flex items-center justify-center 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'"
|
||||
@@ -111,12 +207,13 @@
|
||||
aria-label="Clear content"
|
||||
@click="closePanel"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-4 h-4 shrink-0" 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>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<ContextLoader
|
||||
@@ -126,7 +223,7 @@
|
||||
>
|
||||
<template #header-actions>
|
||||
<button
|
||||
class="p-2 rounded-lg transition-colors -mr-1"
|
||||
class="flex items-center justify-center 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'"
|
||||
@@ -134,7 +231,7 @@
|
||||
aria-label="Clear content"
|
||||
@click="closePanel"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-4 h-4 shrink-0" 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>
|
||||
@@ -186,11 +283,14 @@ import { useAI } from '@/composables/useAI'
|
||||
import { useContentPanel } from '@/composables/useContentPanel'
|
||||
import ChatWindow from '@/components/chat/ChatWindow.vue'
|
||||
import FilmGrid from '@/components/content/FilmGrid.vue'
|
||||
import ArticleDetail from '@/components/content/ArticleDetail.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 MagazineGrid from '@/components/content/MagazineGrid.vue'
|
||||
import NewsGrid from '@/components/content/NewsGrid.vue'
|
||||
import ContextLoader from '@/components/content/ContextLoader.vue'
|
||||
import PlayerBar from '@/components/player/PlayerBar.vue'
|
||||
import { usePlayer } from '@/composables/usePlayer'
|
||||
@@ -206,17 +306,28 @@ const {
|
||||
panelFilms,
|
||||
panelSongs,
|
||||
panelPodcasts,
|
||||
panelWebResults,
|
||||
panelWebsites,
|
||||
panelMagazineSections,
|
||||
panelMagazineHeroImage,
|
||||
panelTitle,
|
||||
panelQuery,
|
||||
contentType,
|
||||
activeTab,
|
||||
availableTabs,
|
||||
setActiveTab,
|
||||
selectedFilm,
|
||||
selectedSong,
|
||||
selectedPodcast,
|
||||
selectedArticle,
|
||||
openFilmDetail,
|
||||
closeFilmDetail,
|
||||
openSongDetail,
|
||||
closeSongDetail,
|
||||
openPodcastDetail,
|
||||
closePodcastDetail,
|
||||
openArticleDetail,
|
||||
closeArticleDetail,
|
||||
closePanel,
|
||||
} = useContentPanel()
|
||||
|
||||
@@ -229,6 +340,7 @@ const loaderContextType = computed(() => {
|
||||
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'
|
||||
if (/\b(website|websites|where to check|best places|check online|resources?|sources?)\b/.test(q)) return 'websites'
|
||||
return contentType.value
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user