feat: complete AIUI integration — all 31 overnight tasks

- Protocol: 10 context categories (apps, system, network, bitcoin, media, files, notes, search, ai-local, wallet)
- ContextBroker: real data wiring for all categories with sanitization
- Permissions: user toggles for all categories in Settings
- Nginx: Claude API, OpenRouter, SearXNG proxy pass-through
- Actions: launch-app, search-web, install-app handlers
- Chat.vue: loading state + connection indicator
- Integration test page: test-aiui.html

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-04 14:34:02 +00:00
co-authored by Claude Opus 4.6
parent 81473db38b
commit 7b56927c3c
26 changed files with 1253 additions and 142 deletions
+61 -4
View File
@@ -1,6 +1,6 @@
<template>
<div class="chat-fullscreen">
<!-- Close button: top-left, glass pill, returns to previous view -->
<!-- Close button + connection indicator -->
<div class="chat-mode-pill">
<button class="chat-close-btn" @click="closeChat">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -8,8 +8,23 @@
</svg>
<span class="text-xs font-medium">Close</span>
</button>
<div
v-if="aiuiConnected"
class="w-2 h-2 rounded-full bg-green-400 ml-2 shadow-[0_0_6px_rgba(74,222,128,0.5)]"
title="AIUI connected"
/>
</div>
<!-- Loading indicator while iframe loads -->
<Transition name="fade">
<div v-if="aiuiUrl && !aiuiConnected" class="chat-loading">
<div class="glass-card p-8 flex flex-col items-center gap-4">
<div class="chat-loading-spinner" />
<p class="text-sm text-white/60">Loading AI assistant...</p>
</div>
</div>
</Transition>
<!-- AIUI iframe -->
<iframe
v-if="aiuiUrl"
@@ -48,18 +63,17 @@ import { ContextBroker } from '@/services/contextBroker'
const router = useRouter()
const aiuiFrame = ref<HTMLIFrameElement | null>(null)
const aiuiConnected = ref(false)
let broker: ContextBroker | null = null
const aiuiUrl = computed(() => {
const envUrl = import.meta.env.VITE_AIUI_URL
if (envUrl) return `${envUrl}?embedded=true`
// Production: served from /aiui/ via nginx proxy
if (import.meta.env.PROD) return '/aiui/?embedded=true'
return ''
})
function closeChat() {
// Go back if there's history, otherwise go to dashboard
if (window.history.length > 1) {
router.back()
} else {
@@ -67,8 +81,16 @@ function closeChat() {
}
}
function onAiuiMessage(event: MessageEvent) {
if (!aiuiUrl.value) return
const msg = event.data
if (msg && msg.type === 'ready') {
aiuiConnected.value = true
}
}
onMounted(() => {
// Start context broker if AIUI URL is available
window.addEventListener('message', onAiuiMessage)
if (aiuiUrl.value) {
broker = new ContextBroker(aiuiFrame, aiuiUrl.value)
broker.start()
@@ -76,7 +98,42 @@ onMounted(() => {
})
onBeforeUnmount(() => {
window.removeEventListener('message', onAiuiMessage)
broker?.stop()
broker = null
})
</script>
<style scoped>
.chat-loading {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
.chat-loading-spinner {
width: 32px;
height: 32px;
border: 3px solid rgba(255, 255, 255, 0.1);
border-top-color: #fb923c;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>