phase 7
This commit is contained in:
77
ui/framework/src/composables/useRegistry.ts
Normal file
77
ui/framework/src/composables/useRegistry.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { ref, type Ref } from 'vue'
|
||||
|
||||
/**
|
||||
* Generic registry composable — fetches typed data from a URL, caches it,
|
||||
* exposes it reactively.
|
||||
*
|
||||
* Use for any data that is loaded once at app init and rarely changes:
|
||||
* stage definitions, config schemas, available models, etc.
|
||||
*
|
||||
* The registry is shared across all consumers (singleton per URL).
|
||||
*/
|
||||
|
||||
const cache = new Map<string, { data: Ref<any>; loading: Ref<boolean>; error: Ref<string | null>; promise: Promise<void> | null }>()
|
||||
|
||||
export function useRegistry<T>(url: string): {
|
||||
data: Ref<T[]>
|
||||
loading: Ref<boolean>
|
||||
error: Ref<string | null>
|
||||
refresh: () => Promise<void>
|
||||
} {
|
||||
if (!cache.has(url)) {
|
||||
const data = ref<T[]>([]) as Ref<T[]>
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
const entry = { data, loading, error, promise: null as Promise<void> | null }
|
||||
cache.set(url, entry)
|
||||
|
||||
async function doFetch() {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const resp = await fetch(url)
|
||||
if (!resp.ok) {
|
||||
error.value = `Failed to fetch registry: ${resp.status}`
|
||||
return
|
||||
}
|
||||
data.value = await resp.json()
|
||||
} catch (e) {
|
||||
error.value = String(e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
entry.promise = doFetch()
|
||||
}
|
||||
|
||||
const entry = cache.get(url)!
|
||||
|
||||
async function refresh() {
|
||||
const data = entry.data
|
||||
const loading = entry.loading
|
||||
const error = entry.error
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const resp = await fetch(url)
|
||||
if (!resp.ok) {
|
||||
error.value = `Failed to fetch registry: ${resp.status}`
|
||||
return
|
||||
}
|
||||
data.value = await resp.json()
|
||||
} catch (e) {
|
||||
error.value = String(e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
data: entry.data as Ref<T[]>,
|
||||
loading: entry.loading,
|
||||
error: entry.error,
|
||||
refresh,
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ export { DataSource, type DataSourceStatus } from './datasources/DataSource'
|
||||
export { SSEDataSource } from './datasources/SSEDataSource'
|
||||
export { StaticDataSource } from './datasources/StaticDataSource'
|
||||
export { useDataSource } from './composables/useDataSource'
|
||||
export { useRegistry } from './composables/useRegistry'
|
||||
|
||||
// Components
|
||||
export { default as Panel } from './components/Panel.vue'
|
||||
|
||||
@@ -20,9 +20,7 @@ const emit = defineEmits<{
|
||||
'open-stage-editor': [stage: string]
|
||||
}>()
|
||||
|
||||
const regionStageSet = computed(() => new Set(props.regionStages ?? [
|
||||
'detect_edges', 'detect_objects', 'run_ocr', 'match_brands', 'escalate_vlm', 'escalate_cloud',
|
||||
]))
|
||||
const regionStageSet = computed(() => new Set(props.regionStages ?? []))
|
||||
|
||||
const statusColors: Record<string, string> = {
|
||||
pending: 'var(--status-idle)',
|
||||
|
||||
Reference in New Issue
Block a user