172 lines
5.2 KiB
TypeScript
172 lines
5.2 KiB
TypeScript
import { ref, readonly } from 'vue'
|
|||
|
|
import { archyBridge } from '@/services/archyBridge'
|
||
|
|
|
||
|
|
type AIContextCategory = 'apps' | 'system' | 'network' | 'wallet' | 'files'
|
||
|
|
|
||
|
|
interface ArchyApp {
|
||
|
|
id: string
|
||
|
|
name: string
|
||
|
|
state: string
|
||
|
|
status: string
|
||
|
|
}
|
||
|
|
|
||
|
|
interface ArchySystemInfo {
|
||
|
|
version?: string
|
||
|
|
name?: string
|
||
|
|
}
|
||
|
|
|
||
|
|
interface ArchyNetworkInfo {
|
||
|
|
connected?: boolean
|
||
|
|
}
|
||
|
|
|
||
|
|
// Singleton reactive state (shared across all components using this composable)
|
||
|
|
const isEmbedded = ref(false)
|
||
|
|
const isInitialized = ref(false)
|
||
|
|
const permissions = ref<AIContextCategory[]>([])
|
||
|
|
const accentColor = ref<string | null>(null)
|
||
|
|
const installedApps = ref<ArchyApp[]>([])
|
||
|
|
const systemInfo = ref<ArchySystemInfo>({})
|
||
|
|
const networkInfo = ref<ArchyNetworkInfo>({})
|
||
|
|
let cleanups: (() => void)[] = []
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Reactive composable wrapping archyBridge for Archy ↔ AIUI integration.
|
||
|
|
* Call `init()` once in App.vue when `?embedded=true` is detected.
|
||
|
|
*/
|
||
|
|
export function useArchy() {
|
||
|
|
/** Initialize the bridge and start listening for Archy messages */
|
||
|
|
function init() {
|
||
|
|
if (isInitialized.value) return
|
||
|
|
|
||
|
|
const embedded = !!(window as unknown as Record<string, unknown>).__AIUI_EMBEDDED__
|
||
|
|
isEmbedded.value = embedded
|
||
|
|
if (!embedded || !archyBridge.isInArchy()) return
|
||
|
|
|
||
|
|
archyBridge.init()
|
||
|
|
isInitialized.value = true
|
||
|
|
|
||
|
|
// Listen for permission updates
|
||
|
|
const unsubPerms = archyBridge.onPermissionsUpdate((cats) => {
|
||
|
|
permissions.value = cats
|
||
|
|
// Auto-fetch context for newly permitted categories
|
||
|
|
fetchPermittedContext(cats)
|
||
|
|
})
|
||
|
|
cleanups.push(unsubPerms)
|
||
|
|
|
||
|
|
// Listen for theme updates
|
||
|
|
const unsubTheme = archyBridge.onThemeUpdate((theme) => {
|
||
|
|
accentColor.value = theme.accent
|
||
|
|
applyAccentColor(theme.accent)
|
||
|
|
})
|
||
|
|
cleanups.push(unsubTheme)
|
||
|
|
|
||
|
|
// Request theme on init
|
||
|
|
archyBridge.requestTheme()
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Fetch context for all permitted categories */
|
||
|
|
async function fetchPermittedContext(cats: AIContextCategory[]) {
|
||
|
|
const fetches: Promise<void>[] = []
|
||
|
|
|
||
|
|
if (cats.includes('apps')) {
|
||
|
|
fetches.push(
|
||
|
|
archyBridge.requestContext('apps').then((res) => {
|
||
|
|
if (res.permitted && Array.isArray(res.data)) {
|
||
|
|
installedApps.value = res.data as ArchyApp[]
|
||
|
|
}
|
||
|
|
}).catch(() => {}),
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
if (cats.includes('system')) {
|
||
|
|
fetches.push(
|
||
|
|
archyBridge.requestContext('system').then((res) => {
|
||
|
|
if (res.permitted && res.data) {
|
||
|
|
systemInfo.value = res.data as ArchySystemInfo
|
||
|
|
}
|
||
|
|
}).catch(() => {}),
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
if (cats.includes('network')) {
|
||
|
|
fetches.push(
|
||
|
|
archyBridge.requestContext('network').then((res) => {
|
||
|
|
if (res.permitted && res.data) {
|
||
|
|
networkInfo.value = res.data as ArchyNetworkInfo
|
||
|
|
}
|
||
|
|
}).catch(() => {}),
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
await Promise.all(fetches)
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Refresh context data (call when user returns to chat) */
|
||
|
|
async function refreshContext() {
|
||
|
|
if (!isInitialized.value) return
|
||
|
|
await fetchPermittedContext(permissions.value)
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Request Archy to perform an action */
|
||
|
|
async function requestAction(action: string, params: Record<string, string> = {}) {
|
||
|
|
if (!isInitialized.value) return { success: false, error: 'Not initialized' }
|
||
|
|
return archyBridge.requestAction(action, params)
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Apply accent color as CSS custom property */
|
||
|
|
function applyAccentColor(color: string) {
|
||
|
|
document.documentElement.style.setProperty('--color-accent', color)
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Build context string for AI system prompt */
|
||
|
|
function buildArchyContext(): string {
|
||
|
|
if (!isInitialized.value) return ''
|
||
|
|
|
||
|
|
const sections: string[] = []
|
||
|
|
|
||
|
|
if (permissions.value.includes('apps') && installedApps.value.length > 0) {
|
||
|
|
const appList = installedApps.value
|
||
|
|
.map((a) => `- ${a.name} (${a.state}${a.status ? ', ' + a.status : ''})`)
|
||
|
|
.join('\n')
|
||
|
|
sections.push(`**Installed apps on this node:**\n${appList}`)
|
||
|
|
}
|
||
|
|
|
||
|
|
if (permissions.value.includes('system') && systemInfo.value.name) {
|
||
|
|
const sys = systemInfo.value
|
||
|
|
sections.push(`**System:** ${sys.name}${sys.version ? ' v' + sys.version : ''}`)
|
||
|
|
}
|
||
|
|
|
||
|
|
if (permissions.value.includes('network')) {
|
||
|
|
const net = networkInfo.value
|
||
|
|
sections.push(`**Network:** ${net.connected ? 'Connected' : 'Disconnected'}`)
|
||
|
|
}
|
||
|
|
|
||
|
|
if (sections.length === 0) return ''
|
||
|
|
|
||
|
|
return `\n\n**Archy Node Context** (this user is running AIUI on their Archipelago node):\n${sections.join('\n')}\n\nYou can help the user manage their node. Available actions: open an app (open-app), install an app (install-app), navigate in Archy (navigate). When recommending apps, check if they're already installed.`
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Clean up on component unmount */
|
||
|
|
function destroy() {
|
||
|
|
for (const cleanup of cleanups) cleanup()
|
||
|
|
cleanups = []
|
||
|
|
archyBridge.destroy()
|
||
|
|
isInitialized.value = false
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
isEmbedded: readonly(isEmbedded),
|
||
|
|
isInitialized: readonly(isInitialized),
|
||
|
|
permissions: readonly(permissions),
|
||
|
|
accentColor: readonly(accentColor),
|
||
|
|
installedApps: readonly(installedApps),
|
||
|
|
systemInfo: readonly(systemInfo),
|
||
|
|
networkInfo: readonly(networkInfo),
|
||
|
|
init,
|
||
|
|
destroy,
|
||
|
|
refreshContext,
|
||
|
|
requestAction,
|
||
|
|
buildArchyContext,
|
||
|
|
}
|
||
|
|
}
|