feat(13-06): AIUI renders Archy content through setArchyContent (D-12)
- archyBridge.ts: content:push case resolving the pending content:request
by id, and requestArchyContent(kind, scope) mirroring requestContext's
shape. Not in the plan's files_modified list, but required to satisfy
Task 3's own instruction to register the content:push handler on the
existing single bridge listener rather than adding a second
window.addEventListener('message') — see SUMMARY deviations.
- useContentPanel.ts: setArchyContent + archyContentActive; guards only
the panelFilms/panelSongs/panelPodcasts assignments inside
updatePanelFromText so Archy-sourced grids stay the source of truth
once populated, per plan scope. Books/TV/images/places/magazine/code/
recipes/news are untouched (13-PATTERNS.md: partial deprecation).
- useArchy.ts: requestArchyContent(kind, scope) calling
archyBridge.requestArchyContent then useContentPanel().setArchyContent.
No FilmGrid/SongGrid/NewsGrid/ContentGridView/content.ts edits (D-12).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ce7a2b2cd4
commit
b771357902
@@ -1,6 +1,8 @@
|
||||
import { ref, readonly } from 'vue'
|
||||
import { archyBridge } from '@/services/archyBridge'
|
||||
import { useTheme } from '@/composables/useTheme'
|
||||
import { useContentPanel } from '@/composables/useContentPanel'
|
||||
import type { Film, Song, Podcast } from '@aiui/core/types/content'
|
||||
import {
|
||||
mockArchyApps, mockArchySystem, mockArchyNetwork,
|
||||
mockArchyWallet, mockArchyBitcoin, mockArchyFiles,
|
||||
@@ -203,6 +205,34 @@ export function useArchy() {
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a batch of grid-ready content from Archy (D-12/D-14, AIUI-03)
|
||||
* and hand it to `useContentPanel`'s `setArchyContent`, which is the
|
||||
* source of truth for `FilmGrid`/`SongGrid` once a node has supplied it.
|
||||
* Mirrors `archyBridge.requestContext`'s permitted/not-permitted shape —
|
||||
* no new convention.
|
||||
*/
|
||||
async function requestArchyContent(
|
||||
kind: 'films' | 'songs' | 'podcasts' | 'all' = 'all',
|
||||
scope: 'own' | 'peers' | 'owned' = 'own',
|
||||
) {
|
||||
if (!isInitialized.value) return
|
||||
try {
|
||||
const res = await archyBridge.requestArchyContent(kind, scope)
|
||||
if (!res.permitted) {
|
||||
console.warn('[AIUI Archy] content: not permitted — user should enable Media/File access in Archy Settings')
|
||||
return
|
||||
}
|
||||
useContentPanel().setArchyContent({
|
||||
films: res.films as Film[],
|
||||
songs: res.songs as Song[],
|
||||
podcasts: res.podcasts as Podcast[],
|
||||
})
|
||||
} catch (err) {
|
||||
console.warn('[AIUI Archy] content fetch failed:', (err as Error)?.message ?? err)
|
||||
}
|
||||
}
|
||||
|
||||
/** Apply accent color as CSS custom property */
|
||||
function applyAccentColor(color: string) {
|
||||
document.documentElement.style.setProperty('--color-accent', color)
|
||||
@@ -302,6 +332,7 @@ export function useArchy() {
|
||||
requestAction,
|
||||
readFile,
|
||||
tailLogs,
|
||||
requestArchyContent,
|
||||
buildArchyContext,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,14 @@ const selectedDesignSystemItem = ref<DesignSystemItem | null>(null)
|
||||
const longFormArticle = ref<{ content: string; title?: string } | null>(null)
|
||||
const pdfUrl = ref<{ url: string; title?: string } | null>(null)
|
||||
const mapPlaces = ref<Place[]>([])
|
||||
/** True once `setArchyContent` has populated panelFilms/panelSongs/
|
||||
* panelPodcasts from a real node (D-12). While true, `updatePanelFromText`
|
||||
* leaves those three buckets alone — the Archy-sourced grids are the
|
||||
* source of truth for them, not the model's own reply text. Every other
|
||||
* bucket (books, TV, images, places, magazine, code, recipes, news) has no
|
||||
* Archy source in this plan's scope and keeps using the regex path
|
||||
* unconditionally (13-PATTERNS.md: partial deprecation, not a removal). */
|
||||
const archyContentActive = ref(false)
|
||||
|
||||
export interface DesignSystemItem {
|
||||
id: string
|
||||
@@ -187,13 +195,18 @@ export function useContentPanel() {
|
||||
const visibleApps = showApps ? apps : []
|
||||
const visibleCodeBlocks = showCode ? codeBlocks : []
|
||||
|
||||
panelFilms.value = visibleFilms
|
||||
// Archy-sourced films/songs/podcasts are the source of truth once a
|
||||
// node has supplied them (D-12) — don't let this turn's regex
|
||||
// extraction of the model's own reply text overwrite them.
|
||||
if (!archyContentActive.value) {
|
||||
panelFilms.value = visibleFilms
|
||||
panelSongs.value = visibleSongs
|
||||
panelPodcasts.value = visiblePodcasts
|
||||
}
|
||||
panelBooks.value = visibleBooks
|
||||
panelTVSeries.value = visibleTVSeries
|
||||
panelImages.value = visibleImages
|
||||
panelPlaces.value = visiblePlaces
|
||||
panelSongs.value = visibleSongs
|
||||
panelPodcasts.value = visiblePodcasts
|
||||
panelWebResults.value = visibleNews
|
||||
panelWebsites.value = visibleWebsites
|
||||
panelRecipes.value = visibleRecipes
|
||||
@@ -265,6 +278,27 @@ export function useContentPanel() {
|
||||
panelOpen.value = tabs.length > 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the film/song/podcast grids directly from a node's real
|
||||
* content (D-12) — `useArchy.ts`'s `requestArchyContent` calls this from
|
||||
* its `content:push` handler. Bypasses `updatePanelFromText`'s regex
|
||||
* path entirely for these three buckets and marks `archyContentActive`
|
||||
* so a later `updatePanelFromText` call (from an unrelated chat turn)
|
||||
* does not clobber them back to a regex-scraped or empty state.
|
||||
*
|
||||
* TMDB posters, web search and RSS remain unavailable on a node (their
|
||||
* Vite plugins are dev-server-only — 13-CONTEXT.md landmines), so a
|
||||
* `Film`/`Song` adapted from peer/own-node data has no `posterUrl`/
|
||||
* `coverUrl`; `FilmGrid`/`SongGrid` already render their existing
|
||||
* no-artwork fallback for that case (unchanged by this plan, D-12).
|
||||
*/
|
||||
function setArchyContent(bundle: { films?: Film[]; songs?: Song[]; podcasts?: Podcast[] }) {
|
||||
panelFilms.value = bundle.films ?? []
|
||||
panelSongs.value = bundle.songs ?? []
|
||||
panelPodcasts.value = bundle.podcasts ?? []
|
||||
archyContentActive.value = true
|
||||
}
|
||||
|
||||
function setActiveTab(tab: ContentTab) {
|
||||
if (availableTabs.value.includes(tab)) activeTab.value = tab
|
||||
}
|
||||
@@ -461,6 +495,8 @@ export function useContentPanel() {
|
||||
panelPlaces,
|
||||
panelSongs,
|
||||
panelPodcasts,
|
||||
archyContentActive,
|
||||
setArchyContent,
|
||||
panelWebResults,
|
||||
panelWebsites,
|
||||
panelRecipes,
|
||||
|
||||
@@ -12,6 +12,20 @@ interface ContextResponse {
|
||||
permitted: boolean
|
||||
}
|
||||
|
||||
/** `kind`/`scope` for a `content:request` — see neode-ui's
|
||||
* `types/aiui-protocol.ts` (`AIUIContentRequest`/`ArchyContentPush`). Archy
|
||||
* decides the RPC method from `scope`; this bundle carries only data, never
|
||||
* a method name (T-13-34). Films/songs/podcasts are typed `unknown[]` here
|
||||
* deliberately — this transport module has no reason to know AIUI's content
|
||||
* shapes; `useArchy.ts`/`useContentPanel.ts` cast them to `Film[]`/`Song[]`/
|
||||
* `Podcast[]` at the one call site that does. */
|
||||
interface ContentResponse {
|
||||
films: unknown[]
|
||||
songs: unknown[]
|
||||
podcasts: unknown[]
|
||||
permitted: boolean
|
||||
}
|
||||
|
||||
export interface ActionResponse {
|
||||
success: boolean
|
||||
error?: string
|
||||
@@ -87,6 +101,20 @@ function handleMessage(event: MessageEvent) {
|
||||
break
|
||||
}
|
||||
|
||||
case 'content:push': {
|
||||
const pending = pendingRequests.get(msg.id)
|
||||
if (pending) {
|
||||
pendingRequests.delete(msg.id)
|
||||
pending.resolve({
|
||||
films: Array.isArray(msg.films) ? msg.films : [],
|
||||
songs: Array.isArray(msg.songs) ? msg.songs : [],
|
||||
podcasts: Array.isArray(msg.podcasts) ? msg.podcasts : [],
|
||||
permitted: msg.permitted !== false,
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'permissions:update': {
|
||||
currentPermissions = msg.categories || []
|
||||
for (const cb of permissionsCallbacks) cb(currentPermissions)
|
||||
@@ -261,6 +289,39 @@ export const archyBridge = {
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Request a batch of grid-ready content from Archy (D-12/D-14, AIUI-03).
|
||||
* `kind` and `scope` are enums the broker interprets — this bridge never
|
||||
* lets AIUI name an RPC method or params. Resolves with `permitted:
|
||||
* false` and empty arrays if the media/files permission categories
|
||||
* aren't granted, rather than rejecting (mirrors `requestContext`'s
|
||||
* shape so `useArchy.ts` can check `.permitted` the same way).
|
||||
*/
|
||||
requestArchyContent(
|
||||
kind: 'films' | 'songs' | 'podcasts' | 'all',
|
||||
scope?: 'own' | 'peers' | 'owned',
|
||||
): Promise<ContentResponse> {
|
||||
const id = generateId()
|
||||
return new Promise((resolve, reject) => {
|
||||
pendingRequests.set(id, { resolve: resolve as (v: unknown) => void, reject })
|
||||
postToParent({
|
||||
type: 'content:request',
|
||||
id,
|
||||
kind,
|
||||
scope,
|
||||
})
|
||||
|
||||
// Peer/owned scopes can fan out across multiple mesh/Tor round trips
|
||||
// node-side — give this more room than the plain context:request's 10s.
|
||||
setTimeout(() => {
|
||||
if (pendingRequests.has(id)) {
|
||||
pendingRequests.delete(id)
|
||||
reject(new Error(`Content request timed out: ${kind}`))
|
||||
}
|
||||
}, 30000)
|
||||
})
|
||||
},
|
||||
|
||||
/** Request Archy's theme info */
|
||||
requestTheme() {
|
||||
postToParent({ type: 'theme:request' })
|
||||
|
||||
Reference in New Issue
Block a user