feat(app): register film and song renderers via plugin system
Create film-renderer.ts and song-renderer.ts as RendererDefinition plugins using defineAsyncComponent for lazy loading. ContentPanel.vue now looks up film/song components via getRendererForContentType() instead of direct imports, enabling future community renderer plugins. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
ad4612a5c2
commit
557f9e6220
@@ -55,8 +55,9 @@
|
||||
<div class="flex-1 min-h-0">
|
||||
<ErrorBoundary title="Content failed to load">
|
||||
<!-- Detail views (override tab content) -->
|
||||
<FilmDetail
|
||||
v-if="selectedFilm"
|
||||
<component
|
||||
:is="filmRenderer?.panelPlay"
|
||||
v-if="selectedFilm && filmRenderer?.panelPlay"
|
||||
:film="selectedFilm"
|
||||
@back="closeFilmDetail"
|
||||
/>
|
||||
@@ -70,8 +71,9 @@
|
||||
:series="selectedTVSeries"
|
||||
@back="closeTVSeriesDetail"
|
||||
/>
|
||||
<SongDetail
|
||||
v-else-if="selectedSong"
|
||||
<component
|
||||
:is="songRenderer?.panelPlay"
|
||||
v-else-if="selectedSong && songRenderer?.panelPlay"
|
||||
:song="selectedSong"
|
||||
@back="closeSongDetail"
|
||||
/>
|
||||
@@ -87,8 +89,9 @@
|
||||
/>
|
||||
|
||||
<!-- Grid views by active tab -->
|
||||
<FilmGrid
|
||||
v-else-if="activeTab === 'film'"
|
||||
<component
|
||||
:is="filmRenderer?.panelPreview"
|
||||
v-else-if="activeTab === 'film' && filmRenderer?.panelPreview"
|
||||
:films="panelFilms"
|
||||
:title="panelTitle"
|
||||
@select-film="openFilmDetail"
|
||||
@@ -105,8 +108,9 @@
|
||||
:title="panelTitle"
|
||||
@select-series="openTVSeriesDetail"
|
||||
/>
|
||||
<SongGrid
|
||||
v-else-if="activeTab === 'song'"
|
||||
<component
|
||||
:is="songRenderer?.panelPreview"
|
||||
v-else-if="activeTab === 'song' && songRenderer?.panelPreview"
|
||||
:songs="panelSongs"
|
||||
:title="panelTitle"
|
||||
@select-song="openSongDetail"
|
||||
@@ -154,14 +158,11 @@
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useTheme } from '@/composables/useTheme'
|
||||
import { useContentPanel, type ContentTab } from '@/composables/useContentPanel'
|
||||
import FilmGrid from './FilmGrid.vue'
|
||||
import FilmDetail from './FilmDetail.vue'
|
||||
import { getRendererForContentType } from '@aiui/core'
|
||||
import BookGrid from './BookGrid.vue'
|
||||
import BookDetail from './BookDetail.vue'
|
||||
import TVSeriesGrid from './TVSeriesGrid.vue'
|
||||
import TVSeriesDetail from './TVSeriesDetail.vue'
|
||||
import SongGrid from './SongGrid.vue'
|
||||
import SongDetail from './SongDetail.vue'
|
||||
import PodcastGrid from './PodcastGrid.vue'
|
||||
import PodcastDetail from './PodcastDetail.vue'
|
||||
import NewsGrid from './NewsGrid.vue'
|
||||
@@ -171,6 +172,10 @@ import ProjectGrid from './ProjectGrid.vue'
|
||||
import NostrGrid from './NostrGrid.vue'
|
||||
import ErrorBoundary from '@/components/ui/ErrorBoundary.vue'
|
||||
|
||||
// Film and song renderers loaded from plugin registry
|
||||
const filmRenderer = computed(() => getRendererForContentType('film'))
|
||||
const songRenderer = computed(() => getRendererForContentType('song'))
|
||||
|
||||
const { isDark } = useTheme()
|
||||
const {
|
||||
panelOpen,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { registerPlugin } from '@aiui/core'
|
||||
import { registerPlugin, registerRenderer } from '@aiui/core'
|
||||
|
||||
export async function initializePlugins(): Promise<void> {
|
||||
// Register built-in AI provider
|
||||
@@ -20,7 +20,14 @@ export async function initializePlugins(): Promise<void> {
|
||||
},
|
||||
})
|
||||
|
||||
// Register built-in content renderers
|
||||
const { filmRenderer } = await import('./renderers/film-renderer')
|
||||
const { songRenderer } = await import('./renderers/song-renderer')
|
||||
registerRenderer(filmRenderer)
|
||||
registerRenderer(songRenderer)
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
console.log('[AIUI] Plugins initialized:', claudeProvider.id)
|
||||
console.log('[AIUI] Renderers registered: film, song')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import type { RendererDefinition } from '@aiui/core'
|
||||
|
||||
export const filmRenderer: RendererDefinition = {
|
||||
id: 'film',
|
||||
name: 'Film Renderer',
|
||||
contentType: 'film',
|
||||
surfaces: ['chat-preview', 'panel-preview', 'panel-play'],
|
||||
chatPreview: defineAsyncComponent(() => import('@/components/content/FilmGrid.vue')),
|
||||
panelPreview: defineAsyncComponent(() => import('@/components/content/FilmGrid.vue')),
|
||||
panelPlay: defineAsyncComponent(() => import('@/components/content/FilmDetail.vue')),
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import type { RendererDefinition } from '@aiui/core'
|
||||
|
||||
export const songRenderer: RendererDefinition = {
|
||||
id: 'song',
|
||||
name: 'Song Renderer',
|
||||
contentType: 'song',
|
||||
surfaces: ['chat-preview', 'panel-preview', 'panel-play'],
|
||||
chatPreview: defineAsyncComponent(() => import('@/components/content/SongGrid.vue')),
|
||||
panelPreview: defineAsyncComponent(() => import('@/components/content/SongGrid.vue')),
|
||||
panelPlay: defineAsyncComponent(() => import('@/components/content/SongDetail.vue')),
|
||||
}
|
||||
Reference in New Issue
Block a user