feat: AIUI chat mode integration with iframe, context broker, overnight loop
- Chat mode: AIUI loads in sandboxed iframe at /dashboard/chat with transparent bg - Mode switcher: Easy + Pro tabs only, Chat is a launcher button - Keyboard shortcuts: Cmd+1 (Easy), Cmd+2 (Pro), Cmd+3 (Chat), Cmd+M (cycle) - Directional transitions: chat slides from/to left, dashboard from/to right - Context broker: postMessage protocol for quarantined AIUI communication - AI permissions store: user-controlled toggles for data access categories - Settings UI: AI Data Access section with per-category toggles - AIUI container manifest and nginx proxy config for /aiui/ - Deploy script builds AIUI with /aiui/ base path - Overnight loop infrastructure (loop.sh, prepare.sh, plan.md, prompt.md) - Security hooks for autonomous overnight runs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
106
neode-ui/src/stores/aiPermissions.ts
Normal file
106
neode-ui/src/stores/aiPermissions.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import type { AIContextCategory } from '@/types/aiui-protocol'
|
||||
|
||||
const STORAGE_KEY = 'archipelago-ai-permissions'
|
||||
|
||||
export interface AIPermissionCategory {
|
||||
id: AIContextCategory
|
||||
label: string
|
||||
description: string
|
||||
icon: string
|
||||
}
|
||||
|
||||
export const AI_PERMISSION_CATEGORIES: AIPermissionCategory[] = [
|
||||
{
|
||||
id: 'apps',
|
||||
label: 'Installed Apps',
|
||||
description: 'App names, status, and health — no credentials or config details',
|
||||
icon: 'M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z',
|
||||
},
|
||||
{
|
||||
id: 'system',
|
||||
label: 'System Stats',
|
||||
description: 'CPU, RAM, disk usage — no file paths or IP addresses',
|
||||
icon: 'M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z',
|
||||
},
|
||||
{
|
||||
id: 'network',
|
||||
label: 'Network Status',
|
||||
description: 'Connection status, peer count — no IP addresses or keys',
|
||||
icon: 'M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01',
|
||||
},
|
||||
{
|
||||
id: 'wallet',
|
||||
label: 'Wallet Overview',
|
||||
description: 'Balance, channel count — no private keys, seeds, or addresses',
|
||||
icon: 'M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z',
|
||||
},
|
||||
{
|
||||
id: 'files',
|
||||
label: 'File Names',
|
||||
description: 'Folder and file names in Cloud — no file contents',
|
||||
icon: 'M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z',
|
||||
},
|
||||
]
|
||||
|
||||
export const useAIPermissionsStore = defineStore('aiPermissions', () => {
|
||||
const enabled = ref<Set<AIContextCategory>>(loadFromStorage())
|
||||
|
||||
function loadFromStorage(): Set<AIContextCategory> {
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY)
|
||||
if (stored) {
|
||||
const parsed = JSON.parse(stored) as AIContextCategory[]
|
||||
return new Set(parsed.filter(c => AI_PERMISSION_CATEGORIES.some(cat => cat.id === c)))
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return new Set()
|
||||
}
|
||||
|
||||
function save() {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify([...enabled.value]))
|
||||
}
|
||||
|
||||
function isEnabled(category: AIContextCategory): boolean {
|
||||
return enabled.value.has(category)
|
||||
}
|
||||
|
||||
function toggle(category: AIContextCategory) {
|
||||
if (enabled.value.has(category)) {
|
||||
enabled.value.delete(category)
|
||||
} else {
|
||||
enabled.value.add(category)
|
||||
}
|
||||
// Trigger reactivity
|
||||
enabled.value = new Set(enabled.value)
|
||||
save()
|
||||
}
|
||||
|
||||
function enableAll() {
|
||||
enabled.value = new Set(AI_PERMISSION_CATEGORIES.map(c => c.id))
|
||||
save()
|
||||
}
|
||||
|
||||
function disableAll() {
|
||||
enabled.value = new Set()
|
||||
save()
|
||||
}
|
||||
|
||||
const enabledCategories = computed(() => [...enabled.value])
|
||||
const allEnabled = computed(() => enabled.value.size === AI_PERMISSION_CATEGORIES.length)
|
||||
const noneEnabled = computed(() => enabled.value.size === 0)
|
||||
|
||||
return {
|
||||
enabled,
|
||||
isEnabled,
|
||||
toggle,
|
||||
enableAll,
|
||||
disableAll,
|
||||
enabledCategories,
|
||||
allEnabled,
|
||||
noneEnabled,
|
||||
}
|
||||
})
|
||||
@@ -25,9 +25,17 @@ export const useUIModeStore = defineStore('uiMode', () => {
|
||||
localStorage.setItem(STORAGE_KEY, newMode)
|
||||
}
|
||||
|
||||
function cycleMode(): UIMode {
|
||||
const order: UIMode[] = ['easy', 'gamer']
|
||||
const idx = order.indexOf(mode.value)
|
||||
const next = order[(idx >= 0 ? idx + 1 : 0) % order.length] as UIMode
|
||||
setMode(next)
|
||||
return next
|
||||
}
|
||||
|
||||
const isGamer = computed(() => mode.value === 'gamer')
|
||||
const isEasy = computed(() => mode.value === 'easy')
|
||||
const isChat = computed(() => mode.value === 'chat')
|
||||
|
||||
return { mode, setMode, syncFromBackend, isGamer, isEasy, isChat }
|
||||
return { mode, setMode, cycleMode, syncFromBackend, isGamer, isEasy, isChat }
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user