feat(app): video player, guide page, free films, PWA cache fix
- Add VideoPlayerOverlay component for free film playback - Add GuidePage with interactive node setup walkthrough - Add freeFilms data catalog with public domain films - Enhance PlayerBar with video support and queue management - Add video player store for overlay state management - Refactor music search plugin (Jamendo integration cleanup) - Add PWA cache version purge mechanism in main.ts - Add PWA icon cache fix skill for Brave/Chrome - Improve content grids: loading states, image fallbacks - Enhance useArchy composable with node context - Update useNostr with relay pool management - Expand chat store with guide conversation support - Add test fixtures for guide and node demo prompts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
c84c0fb424
commit
b77c93607a
@@ -31,6 +31,47 @@ const podcastContext = mockPodcasts.map((p) =>
|
||||
`- [${p.id}] "${p.title}" by ${p.host ?? 'Unknown'}${p.year ? ` (${p.year})` : ''} | ${(p.genres ?? []).join(', ')} | On: ${p.sources.map(x => x.type).join(', ')}`
|
||||
).join('\n')
|
||||
|
||||
// ─── Wavlake catalog context (fetched at runtime) ────────────
|
||||
interface WavlakeCatalogTrack {
|
||||
title?: string
|
||||
artist?: string
|
||||
albumTitle?: string
|
||||
duration?: number
|
||||
}
|
||||
|
||||
const wavlakeCatalog = ref<WavlakeCatalogTrack[]>([])
|
||||
let wavlakeFetchedAt = 0
|
||||
const WAVLAKE_REFRESH_INTERVAL = 30 * 60 * 1000 // 30 minutes
|
||||
|
||||
async function refreshWavlakeCatalog() {
|
||||
if (Date.now() - wavlakeFetchedAt < WAVLAKE_REFRESH_INTERVAL && wavlakeCatalog.value.length > 0) return
|
||||
try {
|
||||
const BASE = import.meta.env.BASE_URL || '/'
|
||||
const res = await fetch(`${BASE}api/music/rankings?days=30&limit=40`)
|
||||
if (!res.ok) return
|
||||
const data = await res.json()
|
||||
if (Array.isArray(data)) {
|
||||
wavlakeCatalog.value = data.map((t: Record<string, unknown>) => ({
|
||||
title: t.title as string,
|
||||
artist: t.artist as string,
|
||||
albumTitle: t.albumTitle as string | undefined,
|
||||
duration: t.duration as number | undefined,
|
||||
}))
|
||||
wavlakeFetchedAt = Date.now()
|
||||
}
|
||||
} catch {
|
||||
// Silently fail — catalog is optional context
|
||||
}
|
||||
}
|
||||
|
||||
function buildWavlakeContext(): string {
|
||||
if (wavlakeCatalog.value.length === 0) return ''
|
||||
const lines = wavlakeCatalog.value.map((t) =>
|
||||
`- "${t.title}" by ${t.artist}${t.albumTitle ? ` (${t.albumTitle})` : ''}`
|
||||
)
|
||||
return `\n\n**Wavlake trending tracks** (these are confirmed playable — prefer recommending from this list when relevant):\n${lines.join('\n')}`
|
||||
}
|
||||
|
||||
const SYSTEM_PROMPT = `You are AIUI, a helpful AI assistant with access to the user's media library (films, songs, and podcasts).
|
||||
|
||||
**News/Factual queries:** When the user asks for "news", "latest", "recent", or current information, lead with a direct answer summarizing the news/facts. You MAY add "For deeper coverage:" with [[podcast_ext:...]] tags only. Do NOT use [[song_ext:...]] or [[film_ext:...]] for news queries—podcasts are the appropriate follow-up. Never substitute an answer with only recommendations.
|
||||
@@ -55,7 +96,7 @@ Prioritize Podcasting 2.0–friendly platforms: Fountain.fm, Podcast Index, Cast
|
||||
|
||||
**Websites / "Best places to check":** When listing resources, places to check online, or websites for the user to visit, use markdown links: [Name](https://full-url). For simple domains use **Name** (domain.com), e.g. **Bitcoin Mailing List** (gnusha.org).
|
||||
|
||||
**Music discovery:** For genre-based requests (e.g. "best math rock"), pick from the user's library when relevant, or use [[song_ext:...]] for others. Prioritize indie-friendly platforms: Wavlake, Bandcamp, Internet Archive, SoundCloud, Odysee, Jamendo.
|
||||
**Music discovery:** All music plays from **Wavlake** — a Lightning-powered, Nostr-native music platform. When recommending songs, prefer tracks from the Wavlake trending list (provided below) since those are confirmed playable. For genre requests, use [[song_ext:Title|Artist]] tags — the UI will search Wavlake automatically. Songs not on Wavlake won't play, so stick to Wavlake artists when you can. The user can zap (tip) artists with Lightning directly through the platform.
|
||||
|
||||
Always include these tags so the UI can render rich cards. Write a brief reason why each is worth checking out.
|
||||
|
||||
@@ -344,6 +385,9 @@ function buildSystemPrompt(chatStore: ReturnType<typeof useChatStore>): string {
|
||||
}
|
||||
}
|
||||
|
||||
// Append Wavlake catalog context
|
||||
prompt += buildWavlakeContext()
|
||||
|
||||
// Append memory facts
|
||||
const memoryStore = useMemoryStore()
|
||||
prompt += memoryStore.buildMemoryContext()
|
||||
@@ -470,6 +514,9 @@ export async function streamWithModel(
|
||||
export function useAI() {
|
||||
const chatStore = useChatStore()
|
||||
|
||||
// Fetch Wavlake catalog on first use (non-blocking)
|
||||
refreshWavlakeCatalog()
|
||||
|
||||
function stopGeneration() {
|
||||
if (currentAbort) {
|
||||
currentAbort.abort()
|
||||
@@ -479,6 +526,9 @@ export function useAI() {
|
||||
}
|
||||
|
||||
async function sendMessage(userText: string, images?: ImageAttachment[]) {
|
||||
// Refresh Wavlake catalog if stale (non-blocking, fire-and-forget)
|
||||
refreshWavlakeCatalog()
|
||||
|
||||
const provider = activeProvider.value
|
||||
currentAbort = new AbortController()
|
||||
const signal = currentAbort.signal
|
||||
|
||||
Reference in New Issue
Block a user