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:
Dorian
2026-03-06 00:56:39 +00:00
co-authored by Claude Opus 4.6
parent c84c0fb424
commit b77c93607a
32 changed files with 3027 additions and 413 deletions
+51 -1
View File
@@ -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.0friendly 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