- F8: Add isReconnecting flag to prevent parallel reconnection attempts - F9: Track JSON parse errors, force reconnect after 3 consecutive failures - F11: Reduce RPC timeout to 15s, add jitter to retry backoff - F12: Add vendor chunk splitting for vue/router/pinia - F13: DOMPurify already applied to QR SVGs — verified - F14: Replace O(n) goals alias lookup with Map-based O(1) - F15: Wrap 7 localStorage.setItem calls in try/catch across 5 stores Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
149 lines
5.1 KiB
TypeScript
149 lines
5.1 KiB
TypeScript
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
|
|
group: 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',
|
|
group: 'Node Data',
|
|
},
|
|
{
|
|
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',
|
|
group: 'Node Data',
|
|
},
|
|
{
|
|
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',
|
|
group: 'Node Data',
|
|
},
|
|
{
|
|
id: 'bitcoin',
|
|
label: 'Bitcoin Node',
|
|
description: 'Block height, sync progress, mempool stats — no wallet keys',
|
|
icon: 'M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
|
|
group: 'Node Data',
|
|
},
|
|
{
|
|
id: 'media',
|
|
label: 'Media Libraries',
|
|
description: 'Local media libraries — film, music, podcast titles and metadata, no file paths',
|
|
icon: 'M7 4v16M17 4v16M3 8h4m10 0h4M3 12h18M3 16h4m10 0h4M4 20h16a1 1 0 001-1V5a1 1 0 00-1-1H4a1 1 0 00-1 1v14a1 1 0 001 1z',
|
|
group: 'Media & Files',
|
|
},
|
|
{
|
|
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',
|
|
group: 'Media & Files',
|
|
},
|
|
{
|
|
id: 'notes',
|
|
label: 'Documents & Notes',
|
|
description: 'Document and note titles — no contents',
|
|
icon: 'M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z',
|
|
group: 'Media & Files',
|
|
},
|
|
{
|
|
id: 'search',
|
|
label: 'Web Search',
|
|
description: 'Web search via your private SearXNG instance',
|
|
icon: 'M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z',
|
|
group: 'AI & Search',
|
|
},
|
|
{
|
|
id: 'ai-local',
|
|
label: 'Local AI Models',
|
|
description: 'Local AI models via Ollama — model names and availability',
|
|
icon: 'M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z',
|
|
group: 'AI & Search',
|
|
},
|
|
{
|
|
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',
|
|
group: 'Financial',
|
|
},
|
|
]
|
|
|
|
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: unknown = JSON.parse(stored)
|
|
if (!Array.isArray(parsed)) return new Set()
|
|
return new Set(parsed.filter((c: unknown) => typeof c === 'string' && AI_PERMISSION_CATEGORIES.some(cat => cat.id === c)) as AIContextCategory[])
|
|
}
|
|
} catch (e) {
|
|
if (import.meta.env.DEV) console.warn('Failed to load AI permissions from storage', e)
|
|
}
|
|
return new Set()
|
|
}
|
|
|
|
function save() {
|
|
try { localStorage.setItem(STORAGE_KEY, JSON.stringify([...enabled.value])) } catch { /* localStorage full or unavailable */ }
|
|
}
|
|
|
|
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,
|
|
}
|
|
})
|