@unctad-ai/voice-agent-registries 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ import type { ReactNode } from 'react';
2
+ export default function CopilotProvider({ children }: {
3
+ children: ReactNode;
4
+ }): import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=CopilotProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CopilotProvider.d.ts","sourceRoot":"","sources":["../src/CopilotProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAMvC,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,2CAM5E"}
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { UIActionRegistryProvider } from './UIActionRegistry';
3
+ import { FormFieldRegistryProvider } from './FormFieldRegistry';
4
+ // CopilotKit removed — useChat (Vercel AI SDK) is standalone, no provider needed.
5
+ // UIActionRegistry and FormFieldRegistry remain as pure React contexts.
6
+ export default function CopilotProvider({ children }) {
7
+ return (_jsx(UIActionRegistryProvider, { children: _jsx(FormFieldRegistryProvider, { children: children }) }));
8
+ }
9
+ //# sourceMappingURL=CopilotProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CopilotProvider.js","sourceRoot":"","sources":["../src/CopilotProvider.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAEhE,kFAAkF;AAClF,wEAAwE;AACxE,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EAAE,QAAQ,EAA2B;IAC3E,OAAO,CACL,KAAC,wBAAwB,cACvB,KAAC,yBAAyB,cAAE,QAAQ,GAA6B,GACxC,CAC5B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,62 @@
1
+ import { type ReactNode } from 'react';
2
+ export type FormFieldType = 'text' | 'email' | 'tel' | 'date' | 'select' | 'radio' | 'checkbox';
3
+ export interface FormFieldOption {
4
+ value: string;
5
+ label: string;
6
+ }
7
+ export interface FormField {
8
+ id: string;
9
+ label: string;
10
+ type: FormFieldType;
11
+ required: boolean;
12
+ value: unknown;
13
+ options?: FormFieldOption[];
14
+ setter: (value: unknown) => void;
15
+ elementRef?: React.RefObject<HTMLElement>;
16
+ /** Step/section name for progressive forms — used by getFormSchema to group fields. */
17
+ group?: string;
18
+ }
19
+ interface FormFieldRegistry {
20
+ register(field: FormField): void;
21
+ unregister(id: string): void;
22
+ getFields(): FormField[];
23
+ /** Returns the field label on success, or null if the field was not found / value was invalid. */
24
+ setValue(id: string, value: unknown): string | null;
25
+ subscribe(listener: () => void): () => void;
26
+ getSnapshot(): FormField[];
27
+ }
28
+ export declare const FormFieldRegistryContext: import("react").Context<FormFieldRegistry | null>;
29
+ export declare function FormFieldRegistryProvider({ children }: {
30
+ children: ReactNode;
31
+ }): import("react/jsx-runtime").JSX.Element;
32
+ export declare function useFormFieldRegistry(): {
33
+ fields: FormField[];
34
+ setValue: (id: string, value: unknown) => string | null;
35
+ register: (field: FormField) => void;
36
+ unregister: (id: string) => void;
37
+ };
38
+ /**
39
+ * Hook — registers a form field in the global registry on mount,
40
+ * unregisters on unmount, and keeps the stored value in sync.
41
+ *
42
+ * The `setter` and `options` arguments are stored in refs so that
43
+ * inline arrow functions and inline arrays do NOT cause infinite
44
+ * re-registration loops. Only `value` changes (and truly static props
45
+ * like id/label/type) trigger a re-registration.
46
+ */
47
+ export declare function useRegisterFormField(config: {
48
+ id: string;
49
+ label: string;
50
+ type: FormFieldType;
51
+ required: boolean;
52
+ value: unknown;
53
+ options?: FormFieldOption[];
54
+ setter: (value: unknown) => void;
55
+ elementRef?: React.RefObject<HTMLElement>;
56
+ /** When false the field is unregistered (hidden sections). Defaults to true. */
57
+ enabled?: boolean;
58
+ /** Step/section name for progressive forms — used by getFormSchema to group fields. */
59
+ group?: string;
60
+ }): void;
61
+ export {};
62
+ //# sourceMappingURL=FormFieldRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FormFieldRegistry.d.ts","sourceRoot":"","sources":["../src/FormFieldRegistry.tsx"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC;AAEhG,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAC5B,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACjC,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IAC1C,uFAAuF;IACvF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,iBAAiB;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC;IACjC,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,IAAI,SAAS,EAAE,CAAC;IACzB,kGAAkG;IAClG,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAAC;IACpD,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAC5C,WAAW,IAAI,SAAS,EAAE,CAAC;CAC5B;AAED,eAAO,MAAM,wBAAwB,mDAAgD,CAAC;AAItF,wBAAgB,yBAAyB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,2CA2F9E;AAED,wBAAgB,oBAAoB;;mBAtGrB,MAAM,SAAS,OAAO,KAAG,MAAM,GAAG,IAAI;sBAJnC,SAAS,KAAG,IAAI;qBACjB,MAAM,KAAG,IAAI;EA0H7B;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE;IAC3C,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAC5B,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACjC,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IAC1C,gFAAgF;IAChF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,uFAAuF;IACvF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,QAkDA"}
@@ -0,0 +1,149 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext, useRef, useCallback, useEffect, useMemo, useSyncExternalStore, } from 'react';
3
+ export const FormFieldRegistryContext = createContext(null);
4
+ const EMPTY_FIELDS = [];
5
+ export function FormFieldRegistryProvider({ children }) {
6
+ const fieldsRef = useRef(new Map());
7
+ const listenersRef = useRef(new Set());
8
+ const snapshotRef = useRef(EMPTY_FIELDS);
9
+ const highlightTimersRef = useRef(new Map());
10
+ // Batched notification — coalesces rapid register/unregister calls (e.g. 30 fields
11
+ // mounting simultaneously on PinRegistration) into a single snapshot + listener flush.
12
+ const pendingNotifyRef = useRef(false);
13
+ const notify = useCallback(() => {
14
+ if (!pendingNotifyRef.current) {
15
+ pendingNotifyRef.current = true;
16
+ queueMicrotask(() => {
17
+ pendingNotifyRef.current = false;
18
+ snapshotRef.current = Array.from(fieldsRef.current.values());
19
+ listenersRef.current.forEach((l) => l());
20
+ });
21
+ }
22
+ }, []);
23
+ const registry = useMemo(() => ({
24
+ register(field) {
25
+ fieldsRef.current.set(field.id, field);
26
+ notify();
27
+ },
28
+ unregister(id) {
29
+ fieldsRef.current.delete(id);
30
+ // Clear any pending highlight timer for this field
31
+ const timer = highlightTimersRef.current.get(id);
32
+ if (timer) {
33
+ clearTimeout(timer);
34
+ highlightTimersRef.current.delete(id);
35
+ }
36
+ notify();
37
+ },
38
+ getFields() {
39
+ return snapshotRef.current;
40
+ },
41
+ setValue(id, value) {
42
+ const field = fieldsRef.current.get(id);
43
+ if (!field)
44
+ return null;
45
+ // Validate select/radio values against declared options
46
+ if ((field.type === 'select' || field.type === 'radio') && field.options) {
47
+ if (!field.options.some((o) => o.value === String(value))) {
48
+ return null;
49
+ }
50
+ }
51
+ field.setter(value);
52
+ // Visual highlight on the element if available and motion is not reduced
53
+ if (field.elementRef?.current &&
54
+ !window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
55
+ const el = field.elementRef.current;
56
+ // Clear any existing timer for this field to prevent flicker on rapid calls
57
+ const existing = highlightTimersRef.current.get(id);
58
+ if (existing)
59
+ clearTimeout(existing);
60
+ el.classList.add('ring-2', 'ring-blue-500/50', 'transition-shadow');
61
+ highlightTimersRef.current.set(id, setTimeout(() => {
62
+ el.classList.remove('ring-2', 'ring-blue-500/50', 'transition-shadow');
63
+ highlightTimersRef.current.delete(id);
64
+ }, 1000));
65
+ }
66
+ return field.label;
67
+ },
68
+ subscribe(listener) {
69
+ listenersRef.current.add(listener);
70
+ return () => listenersRef.current.delete(listener);
71
+ },
72
+ getSnapshot() {
73
+ return snapshotRef.current;
74
+ },
75
+ }), [notify]);
76
+ return (_jsx(FormFieldRegistryContext.Provider, { value: registry, children: children }));
77
+ }
78
+ export function useFormFieldRegistry() {
79
+ const registry = useContext(FormFieldRegistryContext);
80
+ if (!registry)
81
+ throw new Error('useFormFieldRegistry must be used within FormFieldRegistryProvider');
82
+ const fields = useSyncExternalStore(registry.subscribe, registry.getSnapshot,
83
+ // SSR-safe: return empty array during server render
84
+ () => EMPTY_FIELDS);
85
+ return {
86
+ fields,
87
+ setValue: registry.setValue,
88
+ register: registry.register,
89
+ unregister: registry.unregister,
90
+ };
91
+ }
92
+ /**
93
+ * Hook — registers a form field in the global registry on mount,
94
+ * unregisters on unmount, and keeps the stored value in sync.
95
+ *
96
+ * The `setter` and `options` arguments are stored in refs so that
97
+ * inline arrow functions and inline arrays do NOT cause infinite
98
+ * re-registration loops. Only `value` changes (and truly static props
99
+ * like id/label/type) trigger a re-registration.
100
+ */
101
+ export function useRegisterFormField(config) {
102
+ const registry = useContext(FormFieldRegistryContext);
103
+ const enabled = config.enabled ?? true;
104
+ // Refs for potentially-unstable callback and array — always kept fresh
105
+ // but never listed as effect dependencies. Updated in a layout-time effect
106
+ // so the latest setter is available before any CopilotKit handler fires.
107
+ const setterRef = useRef(config.setter);
108
+ const optionsRef = useRef(config.options);
109
+ useEffect(() => {
110
+ setterRef.current = config.setter;
111
+ optionsRef.current = config.options;
112
+ });
113
+ // Stable setter wrapper — created once, delegates to the latest setter via ref.
114
+ const stableSetter = useCallback((v) => setterRef.current(v), []);
115
+ useEffect(() => {
116
+ if (!registry)
117
+ return;
118
+ if (!enabled) {
119
+ registry.unregister(config.id);
120
+ return;
121
+ }
122
+ registry.register({
123
+ id: config.id,
124
+ label: config.label,
125
+ type: config.type,
126
+ required: config.required,
127
+ value: config.value,
128
+ options: optionsRef.current,
129
+ setter: stableSetter,
130
+ elementRef: config.elementRef,
131
+ group: config.group,
132
+ });
133
+ return () => registry.unregister(config.id);
134
+ }, [
135
+ config.id,
136
+ config.label,
137
+ config.type,
138
+ config.required,
139
+ config.value,
140
+ // setter excluded — stored in ref, accessed via stableSetter
141
+ // options excluded — stored in ref, read at registration time
142
+ config.elementRef,
143
+ config.group,
144
+ stableSetter,
145
+ registry,
146
+ enabled,
147
+ ]);
148
+ }
149
+ //# sourceMappingURL=FormFieldRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FormFieldRegistry.js","sourceRoot":"","sources":["../src/FormFieldRegistry.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ import { type ReactNode } from 'react';
2
+ export interface UIActionParam {
3
+ name: string;
4
+ description: string;
5
+ type: string;
6
+ }
7
+ export interface UIAction {
8
+ id: string;
9
+ description: string;
10
+ category: string;
11
+ handler: (params?: Record<string, unknown>) => unknown;
12
+ params?: UIActionParam[];
13
+ }
14
+ export declare function UIActionRegistryProvider({ children }: {
15
+ children: ReactNode;
16
+ }): import("react/jsx-runtime").JSX.Element;
17
+ export declare function useUIActionRegistry(): {
18
+ actions: UIAction[];
19
+ execute: (id: string, params?: Record<string, unknown>) => unknown;
20
+ register: (action: UIAction) => void;
21
+ unregister: (id: string) => void;
22
+ };
23
+ /**
24
+ * Hook — registers a UI action in the global registry on mount,
25
+ * unregisters on unmount.
26
+ *
27
+ * The `handler` and `params` arguments are stored in refs so that
28
+ * inline arrow functions and inline arrays do NOT cause infinite
29
+ * re-registration loops. Only truly static props (id, description,
30
+ * category) trigger a re-registration.
31
+ */
32
+ export declare function useRegisterUIAction(id: string, description: string, handler: (params?: Record<string, unknown>) => unknown, options?: {
33
+ category?: string;
34
+ params?: UIActionParam[];
35
+ }): void;
36
+ /**
37
+ * Registers a view-mode toggle action (table/card) for a dashboard section.
38
+ *
39
+ * @param prefix Action ID prefix, e.g. `'dashboard.taxes'`
40
+ * @param label Human-readable label for the section, e.g. `'tax registrations'`
41
+ * @param setViewMode State setter for `'table' | 'card'`
42
+ */
43
+ export declare function useRegisterViewModeAction(prefix: string, label: string, setViewMode: (mode: 'table' | 'card') => void): void;
44
+ /**
45
+ * Registers a tab-switching action.
46
+ *
47
+ * @param prefix Action ID prefix, e.g. `'film'` → registers `'film.switchTab'`
48
+ * @param validTabs Array of valid tab values
49
+ * @param setActiveTab State setter
50
+ * @param category Optional category (defaults to prefix)
51
+ */
52
+ export declare function useRegisterTabSwitchAction(prefix: string, validTabs: readonly string[], setActiveTab: (tab: string) => void, category?: string): void;
53
+ /**
54
+ * Registers a form-submit action with optional guard.
55
+ *
56
+ * @param prefix Action ID prefix, e.g. `'pin-reg'` → registers `'pin-reg.submitApplication'`
57
+ * @param options Configuration object
58
+ */
59
+ export declare function useRegisterSubmitAction(prefix: string, options: {
60
+ description?: string;
61
+ guard?: () => string | null;
62
+ onSubmit: () => void;
63
+ successMessage?: string;
64
+ category?: string;
65
+ }): void;
66
+ //# sourceMappingURL=UIActionRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UIActionRegistry.d.ts","sourceRoot":"","sources":["../src/UIActionRegistry.tsx"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;IACvD,MAAM,CAAC,EAAE,aAAa,EAAE,CAAC;CAC1B;AAeD,wBAAgB,wBAAwB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,2CAsD7E;AAED,wBAAgB,mBAAmB;;kBAlErB,MAAM,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,OAAO;uBAF7C,QAAQ,KAAG,IAAI;qBACjB,MAAM,KAAG,IAAI;EAmF7B;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,MAAM,EACV,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,EACtD,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,aAAa,EAAE,CAAA;CAAE,QA0B1D;AAMD;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,KAAK,IAAI,QAgB9C;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,EACnC,QAAQ,CAAC,EAAE,MAAM,QAsBlB;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE;IACP,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,QAkBF"}
@@ -0,0 +1,163 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext, useRef, useCallback, useEffect, useMemo, useSyncExternalStore, } from 'react';
3
+ const UIActionRegistryContext = createContext(null);
4
+ const EMPTY_ACTIONS = [];
5
+ export function UIActionRegistryProvider({ children }) {
6
+ const actionsRef = useRef(new Map());
7
+ const listenersRef = useRef(new Set());
8
+ const snapshotRef = useRef(EMPTY_ACTIONS);
9
+ // Batched notification — coalesces rapid register/unregister calls into a
10
+ // single snapshot + listener flush (same pattern as FormFieldRegistry).
11
+ const pendingNotifyRef = useRef(false);
12
+ const notify = useCallback(() => {
13
+ if (!pendingNotifyRef.current) {
14
+ pendingNotifyRef.current = true;
15
+ queueMicrotask(() => {
16
+ pendingNotifyRef.current = false;
17
+ snapshotRef.current = Array.from(actionsRef.current.values());
18
+ listenersRef.current.forEach((l) => l());
19
+ });
20
+ }
21
+ }, []);
22
+ // useMemo (not useRef) avoids the "Cannot access refs during render" lint error.
23
+ // The registry object is stable across renders because it closes over refs.
24
+ const registry = useMemo(() => ({
25
+ register(action) {
26
+ actionsRef.current.set(action.id, action);
27
+ notify();
28
+ },
29
+ unregister(id) {
30
+ actionsRef.current.delete(id);
31
+ notify();
32
+ },
33
+ execute(id, params) {
34
+ const action = actionsRef.current.get(id);
35
+ if (!action)
36
+ return `Action "${id}" not found`;
37
+ return action.handler(params);
38
+ },
39
+ getActions() {
40
+ return snapshotRef.current;
41
+ },
42
+ subscribe(listener) {
43
+ listenersRef.current.add(listener);
44
+ return () => listenersRef.current.delete(listener);
45
+ },
46
+ getSnapshot() {
47
+ return snapshotRef.current;
48
+ },
49
+ }), [notify]);
50
+ return (_jsx(UIActionRegistryContext.Provider, { value: registry, children: children }));
51
+ }
52
+ export function useUIActionRegistry() {
53
+ const registry = useContext(UIActionRegistryContext);
54
+ if (!registry)
55
+ throw new Error('useUIActionRegistry must be used within UIActionRegistryProvider');
56
+ const actions = useSyncExternalStore(registry.subscribe, registry.getSnapshot, () => EMPTY_ACTIONS);
57
+ return {
58
+ actions,
59
+ execute: registry.execute,
60
+ register: registry.register,
61
+ unregister: registry.unregister,
62
+ };
63
+ }
64
+ /**
65
+ * Hook — registers a UI action in the global registry on mount,
66
+ * unregisters on unmount.
67
+ *
68
+ * The `handler` and `params` arguments are stored in refs so that
69
+ * inline arrow functions and inline arrays do NOT cause infinite
70
+ * re-registration loops. Only truly static props (id, description,
71
+ * category) trigger a re-registration.
72
+ */
73
+ export function useRegisterUIAction(id, description, handler, options) {
74
+ const registry = useContext(UIActionRegistryContext);
75
+ // Refs for potentially-unstable callback and params array
76
+ const handlerRef = useRef(handler);
77
+ const paramsRef = useRef(options?.params);
78
+ useEffect(() => {
79
+ handlerRef.current = handler;
80
+ paramsRef.current = options?.params;
81
+ });
82
+ // Stable handler wrapper — created once, delegates to the latest handler via ref
83
+ const stableHandler = useCallback((p) => handlerRef.current(p), []);
84
+ useEffect(() => {
85
+ if (!registry)
86
+ return;
87
+ registry.register({
88
+ id,
89
+ description,
90
+ category: options?.category ?? 'general',
91
+ handler: stableHandler,
92
+ params: paramsRef.current,
93
+ });
94
+ return () => registry.unregister(id);
95
+ }, [id, description, options?.category, stableHandler, registry]);
96
+ }
97
+ // ---------------------------------------------------------------------------
98
+ // Convenience hooks — reduce boilerplate for common action patterns
99
+ // ---------------------------------------------------------------------------
100
+ /**
101
+ * Registers a view-mode toggle action (table/card) for a dashboard section.
102
+ *
103
+ * @param prefix Action ID prefix, e.g. `'dashboard.taxes'`
104
+ * @param label Human-readable label for the section, e.g. `'tax registrations'`
105
+ * @param setViewMode State setter for `'table' | 'card'`
106
+ */
107
+ export function useRegisterViewModeAction(prefix, label, setViewMode) {
108
+ useRegisterUIAction(`${prefix}.setViewMode`, `Switch between table and card view for ${label}`, (params) => {
109
+ const mode = params?.mode;
110
+ if (mode !== 'table' && mode !== 'card')
111
+ return 'Invalid mode. Use "table" or "card"';
112
+ setViewMode(mode);
113
+ return `Switched ${label} to ${mode} view`;
114
+ }, {
115
+ category: 'dashboard',
116
+ params: [{ name: 'mode', description: 'View mode: table or card', type: 'string' }],
117
+ });
118
+ }
119
+ /**
120
+ * Registers a tab-switching action.
121
+ *
122
+ * @param prefix Action ID prefix, e.g. `'film'` → registers `'film.switchTab'`
123
+ * @param validTabs Array of valid tab values
124
+ * @param setActiveTab State setter
125
+ * @param category Optional category (defaults to prefix)
126
+ */
127
+ export function useRegisterTabSwitchAction(prefix, validTabs, setActiveTab, category) {
128
+ useRegisterUIAction(`${prefix}.switchTab`, `Switch between ${validTabs.join(', ')} tabs`, (params) => {
129
+ const tab = params?.tab;
130
+ if (!validTabs.includes(tab))
131
+ return `Invalid tab "${tab}". Use: ${validTabs.join(', ')}.`;
132
+ setActiveTab(tab);
133
+ return `Switched to ${tab} tab`;
134
+ }, {
135
+ category: category ?? prefix,
136
+ params: [
137
+ {
138
+ name: 'tab',
139
+ description: `Tab to switch to: ${validTabs.join(', ')}`,
140
+ type: 'string',
141
+ },
142
+ ],
143
+ });
144
+ }
145
+ /**
146
+ * Registers a form-submit action with optional guard.
147
+ *
148
+ * @param prefix Action ID prefix, e.g. `'pin-reg'` → registers `'pin-reg.submitApplication'`
149
+ * @param options Configuration object
150
+ */
151
+ export function useRegisterSubmitAction(prefix, options) {
152
+ useRegisterUIAction(`${prefix}.submitApplication`, options.description ?? 'Submit the application', () => {
153
+ if (options.guard) {
154
+ const err = options.guard();
155
+ if (err)
156
+ return err;
157
+ }
158
+ options.onSubmit();
159
+ return (options.successMessage ??
160
+ 'Application submitted successfully. The user has been redirected to the dashboard.');
161
+ }, { category: options.category ?? prefix });
162
+ }
163
+ //# sourceMappingURL=UIActionRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UIActionRegistry.js","sourceRoot":"","sources":["../src/UIActionRegistry.tsx"],"names":[],"mappings":";AAAA,OAAO,EACL,aAAa,EACb,UAAU,EACV,MAAM,EACN,WAAW,EACX,SAAS,EACT,OAAO,EACP,oBAAoB,GAErB,MAAM,OAAO,CAAC;AAyBf,MAAM,uBAAuB,GAAG,aAAa,CAA0B,IAAI,CAAC,CAAC;AAE7E,MAAM,aAAa,GAAe,EAAE,CAAC;AAErC,MAAM,UAAU,wBAAwB,CAAC,EAAE,QAAQ,EAA2B;IAC5E,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,GAAG,EAAoB,CAAC,CAAC;IACvD,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,GAAG,EAAc,CAAC,CAAC;IACnD,MAAM,WAAW,GAAG,MAAM,CAAa,aAAa,CAAC,CAAC;IAEtD,0EAA0E;IAC1E,wEAAwE;IACxE,MAAM,gBAAgB,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE;QAC9B,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC;YAC9B,gBAAgB,CAAC,OAAO,GAAG,IAAI,CAAC;YAChC,cAAc,CAAC,GAAG,EAAE;gBAClB,gBAAgB,CAAC,OAAO,GAAG,KAAK,CAAC;gBACjC,WAAW,CAAC,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;gBAC9D,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;YAC3C,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,iFAAiF;IACjF,4EAA4E;IAE5E,MAAM,QAAQ,GAAG,OAAO,CACtB,GAAG,EAAE,CAAC,CAAC;QACL,QAAQ,CAAC,MAAgB;YACvB,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;YAC1C,MAAM,EAAE,CAAC;QACX,CAAC;QACD,UAAU,CAAC,EAAU;YACnB,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC9B,MAAM,EAAE,CAAC;QACX,CAAC;QACD,OAAO,CAAC,EAAU,EAAE,MAAgC;YAClD,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC1C,IAAI,CAAC,MAAM;gBAAE,OAAO,WAAW,EAAE,aAAa,CAAC;YAC/C,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAChC,CAAC;QACD,UAAU;YACR,OAAO,WAAW,CAAC,OAAO,CAAC;QAC7B,CAAC;QACD,SAAS,CAAC,QAAoB;YAC5B,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACnC,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACrD,CAAC;QACD,WAAW;YACT,OAAO,WAAW,CAAC,OAAO,CAAC;QAC7B,CAAC;KACF,CAAC,EACF,CAAC,MAAM,CAAC,CACT,CAAC;IAEF,OAAO,CACL,KAAC,uBAAuB,CAAC,QAAQ,IAAC,KAAK,EAAE,QAAQ,YAAG,QAAQ,GAAoC,CACjG,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,MAAM,QAAQ,GAAG,UAAU,CAAC,uBAAuB,CAAC,CAAC;IACrD,IAAI,CAAC,QAAQ;QACX,MAAM,IAAI,KAAK,CAAC,kEAAkE,CAAC,CAAC;IAEtF,MAAM,OAAO,GAAG,oBAAoB,CAClC,QAAQ,CAAC,SAAS,EAClB,QAAQ,CAAC,WAAW,EACpB,GAAG,EAAE,CAAC,aAAa,CACpB,CAAC;IACF,OAAO;QACL,OAAO;QACP,OAAO,EAAE,QAAQ,CAAC,OAAO;QACzB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;QAC3B,UAAU,EAAE,QAAQ,CAAC,UAAU;KAChC,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,EAAU,EACV,WAAmB,EACnB,OAAsD,EACtD,OAAyD;IAEzD,MAAM,QAAQ,GAAG,UAAU,CAAC,uBAAuB,CAAC,CAAC;IAErD,0DAA0D;IAC1D,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IACnC,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC1C,SAAS,CAAC,GAAG,EAAE;QACb,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC;QAC7B,SAAS,CAAC,OAAO,GAAG,OAAO,EAAE,MAAM,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,iFAAiF;IACjF,MAAM,aAAa,GAAG,WAAW,CAAC,CAAC,CAA2B,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAE9F,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,QAAQ;YAAE,OAAO;QACtB,QAAQ,CAAC,QAAQ,CAAC;YAChB,EAAE;YACF,WAAW;YACX,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,SAAS;YACxC,OAAO,EAAE,aAAa;YACtB,MAAM,EAAE,SAAS,CAAC,OAAO;SAC1B,CAAC,CAAC;QACH,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IACvC,CAAC,EAAE,CAAC,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,QAAQ,CAAC,CAAC,CAAC;AACpE,CAAC;AAED,8EAA8E;AAC9E,oEAAoE;AACpE,8EAA8E;AAE9E;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CACvC,MAAc,EACd,KAAa,EACb,WAA6C;IAE7C,mBAAmB,CACjB,GAAG,MAAM,cAAc,EACvB,0CAA0C,KAAK,EAAE,EACjD,CAAC,MAAgC,EAAE,EAAE;QACnC,MAAM,IAAI,GAAG,MAAM,EAAE,IAAc,CAAC;QACpC,IAAI,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,MAAM;YAAE,OAAO,qCAAqC,CAAC;QACtF,WAAW,CAAC,IAAI,CAAC,CAAC;QAClB,OAAO,YAAY,KAAK,OAAO,IAAI,OAAO,CAAC;IAC7C,CAAC,EACD;QACE,QAAQ,EAAE,WAAW;QACrB,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,0BAA0B,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;KACpF,CACF,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,0BAA0B,CACxC,MAAc,EACd,SAA4B,EAC5B,YAAmC,EACnC,QAAiB;IAEjB,mBAAmB,CACjB,GAAG,MAAM,YAAY,EACrB,kBAAkB,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAC7C,CAAC,MAAgC,EAAE,EAAE;QACnC,MAAM,GAAG,GAAG,MAAM,EAAE,GAAa,CAAC;QAClC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,OAAO,gBAAgB,GAAG,WAAW,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;QAC3F,YAAY,CAAC,GAAG,CAAC,CAAC;QAClB,OAAO,eAAe,GAAG,MAAM,CAAC;IAClC,CAAC,EACD;QACE,QAAQ,EAAE,QAAQ,IAAI,MAAM;QAC5B,MAAM,EAAE;YACN;gBACE,IAAI,EAAE,KAAK;gBACX,WAAW,EAAE,qBAAqB,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;gBACxD,IAAI,EAAE,QAAQ;aACf;SACF;KACF,CACF,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CACrC,MAAc,EACd,OAMC;IAED,mBAAmB,CACjB,GAAG,MAAM,oBAAoB,EAC7B,OAAO,CAAC,WAAW,IAAI,wBAAwB,EAC/C,GAAG,EAAE;QACH,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YAClB,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;YAC5B,IAAI,GAAG;gBAAE,OAAO,GAAG,CAAC;QACtB,CAAC;QACD,OAAO,CAAC,QAAQ,EAAE,CAAC;QACnB,OAAO,CACL,OAAO,CAAC,cAAc;YACtB,oFAAoF,CACrF,CAAC;IACJ,CAAC,EACD,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,MAAM,EAAE,CACzC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { SiteConfig } from '@unctad-ai/voice-agent-core';
2
+ import type { FormField } from './FormFieldRegistry';
3
+ interface ClientToolDeps {
4
+ navigate: (path: string) => void;
5
+ executeUIAction: (actionId: string, params?: Record<string, unknown>) => string | undefined | Promise<string | undefined>;
6
+ getFormFields: () => FormField[];
7
+ setFormValue: (id: string, value: unknown) => string | null;
8
+ config: SiteConfig;
9
+ }
10
+ export declare function createClientToolHandler(deps: ClientToolDeps): (toolName: string, args: Record<string, unknown>) => Promise<string>;
11
+ export {};
12
+ //# sourceMappingURL=clientToolHandlers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clientToolHandlers.d.ts","sourceRoot":"","sources":["../src/clientToolHandlers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAKrD,UAAU,cAAc;IACtB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,eAAe,EAAE,CACf,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC7B,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACtD,aAAa,EAAE,MAAM,SAAS,EAAE,CAAC;IACjC,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,CAAC;IAC5D,MAAM,EAAE,UAAU,CAAC;CACpB;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,cAAc,IAG5C,UAAU,MAAM,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,OAAO,CAAC,MAAM,CAAC,CAgGhF"}
@@ -0,0 +1,111 @@
1
+ /** Values the LLM might produce for checkbox "true". */
2
+ const TRUTHY = new Set(['true', 'yes', '1', 'on']);
3
+ export function createClientToolHandler(deps) {
4
+ const { navigate, executeUIAction, getFormFields, setFormValue, config } = deps;
5
+ return async (toolName, args) => {
6
+ switch (toolName) {
7
+ case 'navigateTo': {
8
+ const page = args.page;
9
+ navigate(config.routeMap[page] || '/');
10
+ return page;
11
+ }
12
+ case 'viewService': {
13
+ const serviceId = args.serviceId;
14
+ const service = config.services.find(s => s.id === serviceId);
15
+ if (!service)
16
+ return 'Service not found';
17
+ navigate(`/service/${serviceId}`);
18
+ return `Navigated to ${service.title} info page.`;
19
+ }
20
+ case 'startApplication': {
21
+ const serviceId = args.serviceId;
22
+ const service = config.services.find(s => s.id === serviceId);
23
+ if (!service)
24
+ return 'Service not found';
25
+ const route = config.getServiceFormRoute(serviceId);
26
+ if (!route) {
27
+ navigate(`/service/${serviceId}`);
28
+ return `No online application form exists for "${service.title}" yet — tell the user clearly that only the information page is available. Navigated to the info page instead.`;
29
+ }
30
+ navigate(route);
31
+ return `Opened "${service.title}" application form. Check UI_ACTIONS for what the user needs to do first — do NOT call getFormSchema yet. Guide the user through the first visible step.`;
32
+ }
33
+ case 'performUIAction': {
34
+ const actionId = args.actionId;
35
+ let params;
36
+ if (args.paramsJson) {
37
+ let jsonStr = args.paramsJson.trim();
38
+ jsonStr = jsonStr.replace(/([{,]\s*)([a-zA-Z_]\w*)\s*:/g, '$1"$2":');
39
+ jsonStr = jsonStr.replace(/,\s*([}\]])/g, '$1');
40
+ try {
41
+ params = JSON.parse(jsonStr);
42
+ }
43
+ catch {
44
+ return `Could not parse params "${args.paramsJson}". Use valid JSON like {"key": "value"}.`;
45
+ }
46
+ }
47
+ const result = await executeUIAction(actionId, params);
48
+ if (!result)
49
+ return actionId.split('.').pop() || actionId;
50
+ const firstSentence = result.split(/\.(?:\s|$)/)[0];
51
+ return firstSentence || result;
52
+ }
53
+ case 'getFormSchema': {
54
+ const fields = getFormFields();
55
+ if (fields.length === 0)
56
+ return 'No form fields are visible right now. The form may need a UI action first — check UI_ACTIONS for the next step (e.g. "Add Director").';
57
+ const fieldToSchema = (f) => ({
58
+ id: f.id,
59
+ label: f.label,
60
+ type: f.type,
61
+ value: f.value ?? null,
62
+ ...(f.options?.length ? { opts: f.options } : {}),
63
+ });
64
+ const hasGroups = fields.some((f) => f.group);
65
+ if (!hasGroups)
66
+ return JSON.stringify(fields.map(fieldToSchema));
67
+ const sectionMap = new Map();
68
+ for (const f of fields) {
69
+ const key = f.group || '_ungrouped';
70
+ const arr = sectionMap.get(key);
71
+ if (arr)
72
+ arr.push(f);
73
+ else
74
+ sectionMap.set(key, [f]);
75
+ }
76
+ return JSON.stringify({
77
+ sections: Array.from(sectionMap.entries()).map(([section, sectionFields]) => ({
78
+ section: section === '_ungrouped' ? 'Other' : section,
79
+ fields: sectionFields.map(fieldToSchema),
80
+ })),
81
+ });
82
+ }
83
+ case 'fillFormFields': {
84
+ const fieldEntries = args.fields;
85
+ const filled = [];
86
+ const errors = [];
87
+ const allFields = getFormFields();
88
+ for (const entry of fieldEntries) {
89
+ const fieldDef = allFields.find((f) => f.id === entry.fieldId);
90
+ const coerced = fieldDef?.type === 'checkbox'
91
+ ? typeof entry.value === 'boolean'
92
+ ? entry.value
93
+ : TRUTHY.has(String(entry.value).toLowerCase())
94
+ : entry.value;
95
+ const result = setFormValue(entry.fieldId, coerced);
96
+ if (result === null)
97
+ errors.push(`Field "${entry.fieldId}" not found or invalid value`);
98
+ else
99
+ filled.push(result);
100
+ }
101
+ if (filled.length === 0)
102
+ return errors.length > 0 ? errors.join('; ') : 'No fields matched';
103
+ const summary = filled.join(', ');
104
+ return errors.length > 0 ? `${summary} (${errors.length} failed)` : summary;
105
+ }
106
+ default:
107
+ return `Unknown tool: ${toolName}`;
108
+ }
109
+ };
110
+ }
111
+ //# sourceMappingURL=clientToolHandlers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clientToolHandlers.js","sourceRoot":"","sources":["../src/clientToolHandlers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export { UIActionRegistryProvider, useUIActionRegistry, useRegisterUIAction, useRegisterViewModeAction, useRegisterTabSwitchAction, useRegisterSubmitAction, } from './UIActionRegistry';
2
+ export type { UIAction, UIActionParam } from './UIActionRegistry';
3
+ export { FormFieldRegistryProvider, useFormFieldRegistry, useRegisterFormField, } from './FormFieldRegistry';
4
+ export type { FormField, FormFieldType, FormFieldOption } from './FormFieldRegistry';
5
+ export { default as CopilotProvider } from './CopilotProvider';
6
+ export { useProgressiveFields } from './useProgressiveFields';
7
+ export type { ProgressiveFieldConfig, ProgressiveStepConfig } from './useProgressiveFields';
8
+ export { createClientToolHandler } from './clientToolHandlers';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,mBAAmB,EACnB,yBAAyB,EACzB,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAElE,OAAO,EACL,yBAAyB,EACzB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAErF,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,YAAY,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC5F,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { UIActionRegistryProvider, useUIActionRegistry, useRegisterUIAction, useRegisterViewModeAction, useRegisterTabSwitchAction, useRegisterSubmitAction, } from './UIActionRegistry';
2
+ export { FormFieldRegistryProvider, useFormFieldRegistry, useRegisterFormField, } from './FormFieldRegistry';
3
+ export { default as CopilotProvider } from './CopilotProvider';
4
+ export { useProgressiveFields } from './useProgressiveFields';
5
+ export { createClientToolHandler } from './clientToolHandlers';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,mBAAmB,EACnB,yBAAyB,EACzB,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EACL,yBAAyB,EACzB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE9D,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,40 @@
1
+ import type { FormFieldType, FormFieldOption } from './FormFieldRegistry';
2
+ export interface ProgressiveFieldConfig {
3
+ id: string;
4
+ label: string;
5
+ type: FormFieldType;
6
+ /** Defaults to false when omitted. */
7
+ required?: boolean;
8
+ options?: FormFieldOption[];
9
+ /** Per-field visibility override — ANDed with step.visible. Defaults to true. */
10
+ visible?: boolean;
11
+ /**
12
+ * [value, setter] tuple — same pattern as useState return.
13
+ * Value is constrained to JSON-safe primitives so the fingerprint comparison
14
+ * (JSON.stringify) never throws or produces non-deterministic output.
15
+ * Inline arrow setters are safe — they are ref-stabilized internally.
16
+ */
17
+ bind: [string | boolean | number | null, (value: unknown) => void];
18
+ /** DOM ref for the visual ring-highlight when the LLM fills this field. */
19
+ elementRef?: React.RefObject<HTMLElement>;
20
+ }
21
+ export interface ProgressiveStepConfig {
22
+ /** Section name shown to the LLM via getFormSchema grouping. */
23
+ step: string;
24
+ /** Whether this step's fields should be registered. */
25
+ visible: boolean;
26
+ fields: ProgressiveFieldConfig[];
27
+ }
28
+ /**
29
+ * Registers progressive form fields with the global FormFieldRegistry.
30
+ *
31
+ * Replaces N individual `useRegisterFormField` + `useCallback` + `useMemo`
32
+ * calls with one declarative config. Uses the FormFieldRegistryContext
33
+ * directly (not hook-per-field) to avoid rules-of-hooks issues with
34
+ * conditional field counts.
35
+ *
36
+ * @param prefix - Field ID prefix (e.g. 'evaluate-investment')
37
+ * @param steps - Step configs with visibility and field definitions
38
+ */
39
+ export declare function useProgressiveFields(prefix: string, steps: ProgressiveStepConfig[]): void;
40
+ //# sourceMappingURL=useProgressiveFields.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useProgressiveFields.d.ts","sourceRoot":"","sources":["../src/useProgressiveFields.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE1E,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,CAAC;IACpB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAC5B,iFAAiF;IACjF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,IAAI,EAAE,CAAC,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC,CAAC;IACnE,2EAA2E;IAC3E,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,qBAAqB;IACpC,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,sBAAsB,EAAE,CAAC;CAClC;AAaD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,qBAAqB,EAAE,QAuGlF"}
@@ -0,0 +1,108 @@
1
+ import { useContext, useEffect, useRef } from 'react';
2
+ import { FormFieldRegistryContext } from './FormFieldRegistry';
3
+ /**
4
+ * Registers progressive form fields with the global FormFieldRegistry.
5
+ *
6
+ * Replaces N individual `useRegisterFormField` + `useCallback` + `useMemo`
7
+ * calls with one declarative config. Uses the FormFieldRegistryContext
8
+ * directly (not hook-per-field) to avoid rules-of-hooks issues with
9
+ * conditional field counts.
10
+ *
11
+ * @param prefix - Field ID prefix (e.g. 'evaluate-investment')
12
+ * @param steps - Step configs with visibility and field definitions
13
+ */
14
+ export function useProgressiveFields(prefix, steps) {
15
+ const registry = useContext(FormFieldRegistryContext);
16
+ // Refs for potentially-unstable callbacks and arrays — kept fresh via a
17
+ // passive effect so the render body stays pure (no ref mutations).
18
+ const settersRef = useRef(new Map());
19
+ const optionsRef = useRef(new Map());
20
+ const wrappersRef = useRef(new Map());
21
+ // ── Pure computation: build flat field list (no side effects) ──
22
+ const flatFields = [];
23
+ const currentIds = new Set();
24
+ for (const step of steps) {
25
+ for (const field of step.fields) {
26
+ const fullId = `${prefix}.${field.id}`;
27
+ currentIds.add(fullId);
28
+ flatFields.push({
29
+ id: fullId,
30
+ label: field.label,
31
+ type: field.type,
32
+ required: field.required ?? false,
33
+ value: field.bind[0],
34
+ enabled: step.visible && (field.visible ?? true),
35
+ group: step.step,
36
+ elementRef: field.elementRef,
37
+ });
38
+ }
39
+ }
40
+ // Lazy-init stable wrapper closures — one per field ID, delegates to the
41
+ // latest setter via ref. Only creates on first encounter (idempotent),
42
+ // which is acceptable during render per React guidelines for lazy init.
43
+ for (const { id } of flatFields) {
44
+ if (!wrappersRef.current.has(id)) {
45
+ wrappersRef.current.set(id, (v) => {
46
+ settersRef.current.get(id)?.(v);
47
+ });
48
+ }
49
+ }
50
+ // ── Passive effect: sync refs + prune stale entries ──
51
+ // Runs after every render to keep refs fresh without mutating them during
52
+ // the render body. Also prunes orphaned IDs when fields are removed from
53
+ // the steps config (prevents memory leaks from accumulated map entries).
54
+ useEffect(() => {
55
+ for (const step of steps) {
56
+ for (const field of step.fields) {
57
+ const fullId = `${prefix}.${field.id}`;
58
+ settersRef.current.set(fullId, field.bind[1]);
59
+ optionsRef.current.set(fullId, field.options);
60
+ }
61
+ }
62
+ for (const id of [...settersRef.current.keys()]) {
63
+ if (!currentIds.has(id)) {
64
+ settersRef.current.delete(id);
65
+ optionsRef.current.delete(id);
66
+ wrappersRef.current.delete(id);
67
+ }
68
+ }
69
+ });
70
+ // ── Fingerprint: detect meaningful changes ──
71
+ // Includes ALL metadata (not just id/value/enabled) so label, type, required,
72
+ // and group changes also trigger re-registration. Values are constrained to
73
+ // JSON-safe primitives via the FieldConfig type, so stringify is deterministic.
74
+ const fingerprint = JSON.stringify(flatFields.map((f) => [f.id, f.label, f.type, f.required, f.value, f.enabled, f.group]));
75
+ // ── Registration effect ──
76
+ // The cleanup function unregisters only the IDs this effect registered, so
77
+ // disabled fields or fields from other components are never clobbered.
78
+ // When a field transitions enabled→disabled, the fingerprint changes, the old
79
+ // cleanup runs (unregistering it), and the new effect skips it.
80
+ useEffect(() => {
81
+ if (!registry)
82
+ return;
83
+ const registeredIds = [];
84
+ for (const field of flatFields) {
85
+ if (!field.enabled)
86
+ continue;
87
+ registry.register({
88
+ id: field.id,
89
+ label: field.label,
90
+ type: field.type,
91
+ required: field.required,
92
+ value: field.value,
93
+ options: optionsRef.current.get(field.id),
94
+ setter: wrappersRef.current.get(field.id),
95
+ group: field.group,
96
+ elementRef: field.elementRef,
97
+ });
98
+ registeredIds.push(field.id);
99
+ }
100
+ return () => {
101
+ for (const id of registeredIds) {
102
+ registry.unregister(id);
103
+ }
104
+ };
105
+ // eslint-disable-next-line react-hooks/exhaustive-deps
106
+ }, [registry, fingerprint]);
107
+ }
108
+ //# sourceMappingURL=useProgressiveFields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useProgressiveFields.js","sourceRoot":"","sources":["../src/useProgressiveFields.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@unctad-ai/voice-agent-registries",
3
+ "version": "0.1.1",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "publishConfig": {
8
+ "access": "public",
9
+ "registry": "https://registry.npmjs.org"
10
+ },
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/unctad-ai/voice-agent-kit.git",
17
+ "directory": "packages/registries"
18
+ },
19
+ "peerDependencies": {
20
+ "react": ">=18",
21
+ "@unctad-ai/voice-agent-core": "0.1.1"
22
+ },
23
+ "devDependencies": {
24
+ "react": "^19.2.4",
25
+ "@types/react": "^19.0.0",
26
+ "typescript": "^5.9.3",
27
+ "@unctad-ai/voice-agent-core": "0.1.1"
28
+ },
29
+ "scripts": {
30
+ "build": "tsc",
31
+ "dev": "tsc --watch",
32
+ "typecheck": "tsc --noEmit"
33
+ },
34
+ "exports": {
35
+ ".": {
36
+ "types": "./dist/index.d.ts",
37
+ "import": "./dist/index.js"
38
+ }
39
+ }
40
+ }