Merge remote-tracking branch 'gitea-ai/gsd/phase-13-aiui-functional-conversational-node-control-and-content-surf'
Demo images / Build & push demo images (push) Successful in 3m14s
Demo images / Build & push demo images (push) Successful in 3m14s
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import type { AIContextCategory } from '@/types/aiui-protocol'
|
||||
import { rpcClient } from '@/api/rpc-client'
|
||||
|
||||
const STORAGE_KEY = 'archipelago-ai-permissions'
|
||||
|
||||
@@ -97,7 +98,66 @@ export const AI_PERMISSION_CATEGORIES: AIPermissionCategory[] = [
|
||||
]
|
||||
|
||||
export const useAIPermissionsStore = defineStore('aiPermissions', () => {
|
||||
// Seeded from localStorage so the toggles paint immediately, then reconciled
|
||||
// with the node in hydrate(). The node is authoritative.
|
||||
const enabled = ref<Set<AIContextCategory>>(loadFromStorage())
|
||||
const hydrated = ref(false)
|
||||
|
||||
/**
|
||||
* Reconcile with the node, which is where these grants actually live.
|
||||
*
|
||||
* They used to live ONLY in localStorage, which is scoped to an origin — and
|
||||
* a node answers on several (LAN address, Tailscale address, <host>.local,
|
||||
* hostname). Granting over one and returning by another showed every switch
|
||||
* off again: not reset, just never set *there*. Reported as "the AI Data
|
||||
* Access settings are not persistent through sessions", and it made a working
|
||||
* content path look broken, because every scope silently returns nothing
|
||||
* without a grant.
|
||||
*
|
||||
* Migration, not replacement: if this browser holds grants and the node holds
|
||||
* none, the local set is pushed UP rather than being wiped by an empty node.
|
||||
* That covers everyone who granted permissions before this change — without
|
||||
* it, upgrading would silently revoke them. The reverse (node has grants,
|
||||
* browser does not) is the normal case on a new device and the node wins.
|
||||
*/
|
||||
async function hydrate(): Promise<void> {
|
||||
try {
|
||||
const res = await rpcClient.call<{ granted?: string[] }>({ method: 'ai.permissions.get' })
|
||||
const remote = new Set(
|
||||
(res.granted ?? []).filter((c): c is AIContextCategory =>
|
||||
AI_PERMISSION_CATEGORIES.some(cat => cat.id === c),
|
||||
),
|
||||
)
|
||||
|
||||
if (remote.size === 0 && enabled.value.size > 0) {
|
||||
await pushToNode()
|
||||
} else {
|
||||
enabled.value = remote
|
||||
save()
|
||||
}
|
||||
} catch (e) {
|
||||
// Offline, or a node too old to know the method: keep whatever
|
||||
// localStorage had. Degrading to the old behaviour is strictly better
|
||||
// than blanking the operator's grants because a request failed.
|
||||
if (import.meta.env.DEV) console.warn('AI permissions: node unreachable, using local', e)
|
||||
} finally {
|
||||
hydrated.value = true
|
||||
}
|
||||
}
|
||||
|
||||
async function pushToNode(): Promise<void> {
|
||||
try {
|
||||
await rpcClient.call({
|
||||
method: 'ai.permissions.set',
|
||||
params: { granted: [...enabled.value] },
|
||||
})
|
||||
} catch (e) {
|
||||
// The local write already happened, so the UI stays consistent with what
|
||||
// the operator just clicked; it will be pushed again on the next change
|
||||
// or the next hydrate().
|
||||
if (import.meta.env.DEV) console.warn('AI permissions: failed to persist to node', e)
|
||||
}
|
||||
}
|
||||
|
||||
function loadFromStorage(): Set<AIContextCategory> {
|
||||
try {
|
||||
@@ -130,16 +190,19 @@ export const useAIPermissionsStore = defineStore('aiPermissions', () => {
|
||||
// Trigger reactivity
|
||||
enabled.value = new Set(enabled.value)
|
||||
save()
|
||||
void pushToNode()
|
||||
}
|
||||
|
||||
function enableAll() {
|
||||
enabled.value = new Set(AI_PERMISSION_CATEGORIES.map(c => c.id))
|
||||
save()
|
||||
void pushToNode()
|
||||
}
|
||||
|
||||
function disableAll() {
|
||||
enabled.value = new Set()
|
||||
save()
|
||||
void pushToNode()
|
||||
}
|
||||
|
||||
const enabledCategories = computed(() => [...enabled.value])
|
||||
@@ -148,6 +211,8 @@ export const useAIPermissionsStore = defineStore('aiPermissions', () => {
|
||||
|
||||
return {
|
||||
enabled,
|
||||
hydrated,
|
||||
hydrate,
|
||||
isEnabled,
|
||||
toggle,
|
||||
enableAll,
|
||||
|
||||
Reference in New Issue
Block a user