import { ref } from 'vue' import type { Film, Song, Podcast, Book, TVSeries, ImageItem, Place } from '@aiui/core/types/content' import type { WebSearchResult } from '@aiui/core/types/message' import { mockFilms } from '@/mocks/films' import { mockSongs } from '@/mocks/songs' import { mockPodcasts } from '@/mocks/podcasts' import { fetchRssFromUrls } from '@/composables/useRssFetch' import { extractAllFilms, extractAllSongs, extractAllPodcasts, extractAllBooks, extractAllTVSeries, extractAllImages, extractAllPlaces, extractMagazineSections, extractMagazineHeroImage, extractMarkdownLinks, extractBoldDomainLinks, mergeNewsResults, extractFilmIds, extractSongIds, extractPodcastIds, stripFilmTags, stripSongTags, stripPodcastTags, stripContentTags, stripMarkdownLinks, } from './contentExtraction' import { isNewsQuery, isNewsLikeResponse, isTVQuery, filterTabsByContext, extractQueryContext, } from './contentFiltering' export type { ContentTab, MagazineSection } from './contentFiltering' import type { ContentTab, MagazineSection } from './contentFiltering' const panelOpen = ref(false) const panelFilms = ref([]) const panelBooks = ref([]) const panelTVSeries = ref([]) const panelWebResults = ref([]) const panelRssArticles = ref([]) const panelWebsites = ref([]) const panelMagazineSections = ref([]) const panelMagazineHeroImage = ref(null) const panelSongs = ref([]) const panelPodcasts = ref([]) const panelImages = ref([]) const panelPlaces = ref([]) const selectedFilm = ref(null) const selectedBook = ref(null) const selectedTVSeries = ref(null) const selectedSong = ref(null) const selectedPodcast = ref(null) const selectedArticle = ref(null) const selectedImage = ref(null) const selectedPlace = ref(null) const selectedWebsite = ref(null) const selectedMagazineSection = ref(null) const magazineSectionIndex = ref(0) const panelTitle = ref('Recommended Films') const panelQuery = ref('') const contentType = ref<'film' | 'song' | 'podcast'>('film') const activeTab = ref('film') const availableTabs = ref([]) const selectedDesignSystemItem = ref(null) const longFormArticle = ref<{ content: string; title?: string } | null>(null) export interface DesignSystemItem { id: string name: string category: 'colors' | 'typography' | 'spacing' | 'atoms' | 'molecules' | 'organisms' description: string code: string preview?: 'inline' usedIn?: string } export function useContentPanel() { function updatePanelFromText(text: string, userQuery = '', webResults: WebSearchResult[] = []) { panelQuery.value = userQuery.trim() const songs = extractAllSongs(text, userQuery) let films = extractAllFilms(text) const podcasts = extractAllPodcasts(text) const books = extractAllBooks(text, userQuery) const tvSeries = extractAllTVSeries(text, userQuery) if (tvSeries.length > 0 && isTVQuery(userQuery)) { films = films.filter(f => !f.id.startsWith('ext-')) } const fromMarkdown = extractMarkdownLinks(text) const boldDomains = extractBoldDomainLinks(text) panelRssArticles.value = [] const hasLinkableContent = fromMarkdown.length > 0 || boldDomains.length > 0 const websitesFromMarkdown = hasLinkableContent ? fromMarkdown : [] const mergedWebsites = mergeNewsResults(websitesFromMarkdown, boldDomains) const hasWebsites = mergedWebsites.length > 0 const images = extractAllImages(text, userQuery) const places = extractAllPlaces(text, userQuery) // Magazine = bullet-style sections (- **Title**: Content) const magazineSections = extractMagazineSections(text) const hasAnyOtherContent = films.length > 0 || songs.length > 0 || podcasts.length > 0 || books.length > 0 || tvSeries.length > 0 || images.length > 0 || places.length > 0 const hasMagazine = magazineSections.length >= 1 && ( isNewsQuery(userQuery) || isNewsLikeResponse(text) || /sentiment|bearish|bull case|macro|%|BTC|bitcoin|BIP|protocol|debate|what'?s happening|ETF|inflow|trading at|key developments|price recovery|institutional|analyst watch|market cap/i.test(text) || (!hasAnyOtherContent && !hasWebsites && magazineSections.length >= 2) ) // News = actual articles (web search + RSS from website domains) const newsContext = isNewsQuery(userQuery) || isNewsLikeResponse(text) const hasNews = (webResults.length > 0 || mergedWebsites.length > 0) && newsContext const mergedNews = hasNews ? mergeNewsResults(webResults, panelRssArticles.value) : [] if (mergedWebsites.length > 0 && newsContext) { const urls = mergedWebsites.map((w) => w.url) fetchRssFromUrls(urls).then((articles) => { if (articles.length === 0) return panelRssArticles.value = articles const combined = mergeNewsResults(panelWebResults.value, articles) panelWebResults.value = combined if (!availableTabs.value.includes('news')) { availableTabs.value = ['news', ...availableTabs.value] activeTab.value = 'news' } const ctx = extractQueryContext(panelQuery.value) panelTitle.value = ctx ? `${ctx} — ${combined.length} articles` : `${combined.length} Articles` }) } const tabs = filterTabsByContext(userQuery, films.length > 0, songs.length > 0, podcasts.length > 0, books.length > 0, tvSeries.length > 0, images.length > 0, places.length > 0, hasNews, hasWebsites, hasMagazine) availableTabs.value = tabs.length > 0 ? tabs : ['film'] activeTab.value = tabs[0] ?? 'film' const showFilms = tabs.includes('film') const showBooks = tabs.includes('book') const showTVSeries = tabs.includes('tvshow') const showImages = tabs.includes('image') const showPlaces = tabs.includes('place') const showSongs = tabs.includes('song') const showPodcasts = tabs.includes('podcast') const showNews = tabs.includes('news') const showWebsitesTab = tabs.includes('websites') const showMagazine = tabs.includes('magazine') const visibleFilms = showFilms ? films : [] const visibleBooks = showBooks ? books : [] const visibleTVSeries = showTVSeries ? tvSeries : [] const visibleImages = showImages ? images : [] const visiblePlaces = showPlaces ? places : [] const visibleSongs = showSongs ? songs : [] const visiblePodcasts = showPodcasts ? podcasts : [] const visibleNews = showNews ? mergedNews : [] const visibleWebsites = showWebsitesTab ? mergedWebsites : [] const visibleMagazineSections = showMagazine ? magazineSections : [] panelFilms.value = visibleFilms panelBooks.value = visibleBooks panelTVSeries.value = visibleTVSeries panelImages.value = visibleImages panelPlaces.value = visiblePlaces panelSongs.value = visibleSongs panelPodcasts.value = visiblePodcasts panelWebResults.value = visibleNews panelWebsites.value = visibleWebsites panelMagazineSections.value = visibleMagazineSections panelMagazineHeroImage.value = showMagazine ? (extractMagazineHeroImage(text) ?? webResults[0]?.imgSrc ?? null) : null selectedFilm.value = null selectedBook.value = null selectedTVSeries.value = null selectedImage.value = null selectedPlace.value = null selectedSong.value = null selectedPodcast.value = null selectedArticle.value = null selectedWebsite.value = null selectedMagazineSection.value = null if (visibleFilms.length > 0) contentType.value = 'film' else if (visibleBooks.length > 0) contentType.value = 'film' else if (visibleTVSeries.length > 0) contentType.value = 'film' else if (visibleSongs.length > 0) contentType.value = 'song' else if (visiblePodcasts.length > 0) contentType.value = 'podcast' else if (visibleNews.length > 0) contentType.value = 'film' else contentType.value = 'film' // Title follows the primary (first) tab const primary = tabs[0] if (primary === 'place' && visiblePlaces.length > 0) { panelTitle.value = visiblePlaces.length === 1 ? visiblePlaces[0].name : `${visiblePlaces.length} Places` } else if (primary === 'tvshow' && visibleTVSeries.length > 0) { panelTitle.value = visibleTVSeries.length === 1 ? visibleTVSeries[0].title : `${visibleTVSeries.length} TV Series` } else if (primary === 'book' && visibleBooks.length > 0) { panelTitle.value = visibleBooks.length === 1 ? visibleBooks[0].title : `${visibleBooks.length} Books` } else if (primary === 'image' && visibleImages.length > 0) { panelTitle.value = `${visibleImages.length} Images` } else if (visibleFilms.length === 1) panelTitle.value = visibleFilms[0].title else if (visibleFilms.length > 1) panelTitle.value = `${visibleFilms.length} Films` else if (visibleBooks.length === 1) panelTitle.value = visibleBooks[0].title else if (visibleBooks.length > 1) panelTitle.value = `${visibleBooks.length} Books` else if (visibleTVSeries.length === 1) panelTitle.value = visibleTVSeries[0].title else if (visibleTVSeries.length > 1) panelTitle.value = `${visibleTVSeries.length} TV Series` else if (visibleSongs.length === 1) panelTitle.value = visibleSongs[0].title else if (visibleSongs.length > 1) panelTitle.value = `${visibleSongs.length} Songs` else if (visiblePlaces.length === 1) panelTitle.value = visiblePlaces[0].name else if (visiblePlaces.length > 1) panelTitle.value = `${visiblePlaces.length} Places` else if (visiblePodcasts.length === 1) panelTitle.value = visiblePodcasts[0].title else if (visiblePodcasts.length > 1) panelTitle.value = `${visiblePodcasts.length} Podcasts` else if (visibleNews.length > 0) { const ctx = extractQueryContext(userQuery) panelTitle.value = ctx ? `${ctx} — ${visibleNews.length} articles` : `${visibleNews.length} Articles` } else if (visibleMagazineSections.length > 0) { const ctx = extractQueryContext(userQuery) panelTitle.value = ctx ? `${ctx} — Brief` : 'AI Brief' } else if (visibleWebsites.length > 0) panelTitle.value = `${visibleWebsites.length} Websites` else panelTitle.value = 'Content' panelOpen.value = tabs.length > 0 } function setActiveTab(tab: ContentTab) { if (availableTabs.value.includes(tab)) activeTab.value = tab } /** Contextual content for inline cards (prompt index badges) */ function getContextualInlineContent(text: string, userQuery: string, webResults: WebSearchResult[] = []) { let films = extractAllFilms(text) const songs = extractAllSongs(text, userQuery) const podcasts = extractAllPodcasts(text) const books = extractAllBooks(text, userQuery) const tvSeries = extractAllTVSeries(text, userQuery) if (tvSeries.length > 0 && isTVQuery(userQuery)) { films = films.filter(f => !f.id.startsWith('ext-')) } const magazineSections = extractMagazineSections(text) const fromMarkdown = extractMarkdownLinks(text) const boldDomains = extractBoldDomainLinks(text) const hasNews = webResults.length > 0 && (isNewsQuery(userQuery) || isNewsLikeResponse(text)) const newsLinks = hasNews ? webResults : [] const hasLinkableContent = fromMarkdown.length > 0 || boldDomains.length > 0 const websitesFromMd = hasLinkableContent ? fromMarkdown : [] const websitesLinks = mergeNewsResults(websitesFromMd, boldDomains) const hasWebsites = websitesLinks.length > 0 const images = extractAllImages(text, userQuery) const places = extractAllPlaces(text, userQuery) const hasAnyOtherInline = films.length > 0 || songs.length > 0 || podcasts.length > 0 || books.length > 0 || tvSeries.length > 0 || images.length > 0 || places.length > 0 const hasMagazine = magazineSections.length >= 1 && ( isNewsQuery(userQuery) || isNewsLikeResponse(text) || /sentiment|bearish|bull case|macro|%|BTC|bitcoin|BIP|protocol|debate|what'?s happening|ETF|inflow|trading at|key developments|price recovery|institutional|analyst watch|market cap/i.test(text) || (!hasAnyOtherInline && !hasWebsites && magazineSections.length >= 2) ) const tabs = filterTabsByContext(userQuery, films.length > 0, songs.length > 0, podcasts.length > 0, books.length > 0, tvSeries.length > 0, images.length > 0, places.length > 0, hasNews, hasWebsites, hasMagazine) return { films: tabs.includes('film') ? films : [], books: tabs.includes('book') ? books : [], tvSeries: tabs.includes('tvshow') ? tvSeries : [], images: tabs.includes('image') ? images : [], places: tabs.includes('place') ? places : [], songs: tabs.includes('song') ? songs : [], podcasts: tabs.includes('podcast') ? podcasts : [], newsLinks: tabs.includes('news') ? newsLinks : [], websitesLinks: tabs.includes('websites') ? websitesLinks : [], magazineSections: tabs.includes('magazine') ? magazineSections : [], } } // ─── Detail open/close ──────────────────────────────────────── function clearAllSelections() { selectedFilm.value = null selectedBook.value = null selectedTVSeries.value = null selectedImage.value = null selectedPlace.value = null selectedSong.value = null selectedPodcast.value = null selectedArticle.value = null selectedWebsite.value = null selectedMagazineSection.value = null selectedDesignSystemItem.value = null longFormArticle.value = null } function openFilmDetail(film: Film) { clearAllSelections(); selectedFilm.value = film } function closeFilmDetail() { selectedFilm.value = null } function openBookDetail(book: Book) { clearAllSelections(); selectedBook.value = book } function closeBookDetail() { selectedBook.value = null } function openSongDetail(song: Song) { clearAllSelections(); selectedSong.value = song } function closeSongDetail() { selectedSong.value = null } function openPodcastDetail(podcast: Podcast) { clearAllSelections(); selectedPodcast.value = podcast } function closePodcastDetail() { selectedPodcast.value = null } function openArticleDetail(article: WebSearchResult) { clearAllSelections(); selectedArticle.value = article; panelOpen.value = true } function closeArticleDetail() { selectedArticle.value = null } function openWebsiteDetail(website: WebSearchResult) { clearAllSelections(); selectedWebsite.value = website; panelOpen.value = true } function closeWebsiteDetail() { selectedWebsite.value = null } function openLongFormArticle(content: string, title?: string) { clearAllSelections(); longFormArticle.value = { content, title }; panelOpen.value = true } function closeLongFormArticle() { longFormArticle.value = null } function openMagazineSectionDetail(section: MagazineSection, index: number) { clearAllSelections() selectedMagazineSection.value = section magazineSectionIndex.value = index } function closeMagazineSectionDetail() { selectedMagazineSection.value = null } function navigateMagazineSection(direction: 'prev' | 'next') { const sections = panelMagazineSections.value if (!sections.length) return let idx = magazineSectionIndex.value idx += direction === 'next' ? 1 : -1 if (idx < 0) idx = sections.length - 1 if (idx >= sections.length) idx = 0 magazineSectionIndex.value = idx selectedMagazineSection.value = sections[idx] } function openTVSeriesDetail(series: TVSeries) { clearAllSelections(); selectedTVSeries.value = series } function closeTVSeriesDetail() { selectedTVSeries.value = null } function openImageDetail(image: ImageItem) { clearAllSelections(); selectedImage.value = image } function closeImageDetail() { selectedImage.value = null } function openPlaceDetail(place: Place) { clearAllSelections(); selectedPlace.value = place } function closePlaceDetail() { selectedPlace.value = null } function openDesignSystemItem(item: DesignSystemItem) { clearAllSelections(); selectedDesignSystemItem.value = item } function closeDesignSystemItem() { selectedDesignSystemItem.value = null } function enterDesignSystemMode() { panelOpen.value = true activeTab.value = 'design-system' availableTabs.value = ['design-system'] panelTitle.value = 'Design System' clearAllSelections() } function closePanel() { panelOpen.value = false clearAllSelections() activeTab.value = 'film' availableTabs.value = [] } function showAllFilms() { panelFilms.value = [...mockFilms] panelSongs.value = [] panelPodcasts.value = [] panelTitle.value = 'Your Film Library' contentType.value = 'film' panelOpen.value = true clearAllSelections() } function showAllSongs() { panelFilms.value = [] panelSongs.value = [...mockSongs] panelPodcasts.value = [] panelTitle.value = 'Your Song Library' contentType.value = 'song' panelOpen.value = true clearAllSelections() } function showAllPodcasts() { panelFilms.value = [] panelSongs.value = [] panelPodcasts.value = [...mockPodcasts] panelTitle.value = 'Your Podcast Library' contentType.value = 'podcast' panelOpen.value = true clearAllSelections() } return { panelOpen, panelFilms, panelBooks, panelTVSeries, panelImages, panelPlaces, panelSongs, panelPodcasts, panelWebResults, panelWebsites, panelMagazineSections, panelMagazineHeroImage, selectedFilm, selectedBook, selectedTVSeries, selectedImage, selectedPlace, selectedSong, selectedPodcast, selectedArticle, selectedWebsite, selectedMagazineSection, magazineSectionIndex, panelTitle, panelQuery, contentType, activeTab, availableTabs, setActiveTab, extractFilmIds, extractAllFilms, extractAllBooks, extractAllTVSeries, extractSongIds, extractAllSongs, extractPodcastIds, extractAllPodcasts, getContextualInlineContent, updatePanelFromText, stripFilmTags, stripSongTags, stripPodcastTags, stripContentTags, stripMarkdownLinks, openFilmDetail, closeFilmDetail, openBookDetail, closeBookDetail, openSongDetail, closeSongDetail, openPodcastDetail, closePodcastDetail, openArticleDetail, closeArticleDetail, openWebsiteDetail, closeWebsiteDetail, openMagazineSectionDetail, closeMagazineSectionDetail, navigateMagazineSection, openTVSeriesDetail, closeTVSeriesDetail, openImageDetail, closeImageDetail, openPlaceDetail, closePlaceDetail, selectedDesignSystemItem, openDesignSystemItem, closeDesignSystemItem, longFormArticle, openLongFormArticle, closeLongFormArticle, enterDesignSystemMode, closePanel, showAllFilms, showAllSongs, showAllPodcasts, } }