@ai11y/react 0.0.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,29 @@
1
+
2
+ > @ai11y/react@0.0.1 build /home/runner/work/ai11y/ai11y/packages/react
3
+ > tsdown
4
+
5
+ ℹ tsdown v0.18.1 powered by rolldown v1.0.0-beta.55
6
+ ℹ config file: /home/runner/work/ai11y/ai11y/packages/react/tsdown.config.ts (unrun)
7
+ ℹ entry: src/index.ts
8
+ ℹ tsconfig: tsconfig.json
9
+ ℹ Build start
10
+ ℹ dist/index.mjs 0.28 kB │ gzip: 0.14 kB
11
+ ℹ dist/hooks/useChat.mjs.map 6.75 kB │ gzip: 2.10 kB
12
+ ℹ dist/components/Ai11yProvider.mjs.map 4.31 kB │ gzip: 1.51 kB
13
+ ℹ dist/hooks/useChat.mjs 3.23 kB │ gzip: 1.05 kB
14
+ ℹ dist/components/Marker.mjs.map 1.95 kB │ gzip: 0.95 kB
15
+ ℹ dist/components/Ai11yProvider.mjs 1.79 kB │ gzip: 0.69 kB
16
+ ℹ dist/components/Ai11yProvider.d.mts.map 0.86 kB │ gzip: 0.39 kB
17
+ ℹ dist/components/Marker.mjs 0.86 kB │ gzip: 0.48 kB
18
+ ℹ dist/hooks/useAi11yContext.mjs.map 0.79 kB │ gzip: 0.46 kB
19
+ ℹ dist/hooks/useChat.d.mts.map 0.77 kB │ gzip: 0.36 kB
20
+ ℹ dist/hooks/useAi11yContext.mjs 0.61 kB │ gzip: 0.35 kB
21
+ ℹ dist/components/Marker.d.mts.map 0.35 kB │ gzip: 0.23 kB
22
+ ℹ dist/hooks/useAi11yContext.d.mts.map 0.18 kB │ gzip: 0.14 kB
23
+ ℹ dist/index.d.mts 0.28 kB │ gzip: 0.14 kB
24
+ ℹ dist/components/Ai11yProvider.d.mts 1.14 kB │ gzip: 0.50 kB
25
+ ℹ dist/hooks/useChat.d.mts 0.80 kB │ gzip: 0.41 kB
26
+ ℹ dist/components/Marker.d.mts 0.55 kB │ gzip: 0.32 kB
27
+ ℹ dist/hooks/useAi11yContext.d.mts 0.47 kB │ gzip: 0.29 kB
28
+ ℹ 18 files, total: 25.95 kB
29
+ ✔ Build complete in 3027ms
package/CHANGELOG.md ADDED
@@ -0,0 +1,39 @@
1
+ # @ai11y/react
2
+
3
+ ## 0.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#10](https://github.com/maerzhase/ai11y/pull/10)
8
+ [`a578f29`](https://github.com/maerzhase/ai11y/commit/a578f296c21cd19c935ff8003442677f7a1cb72d)
9
+ Thanks [@maerzhase](https://github.com/maerzhase)! - Docs and demo
10
+ improvements: naming (ai11y ≠ a11y), Describe/Plan/Act boundaries, UI context
11
+ payload viewer, annotation guidance, Why not ARIA, Security & privacy,
12
+ multi-step demo
13
+
14
+ - [#10](https://github.com/maerzhase/ai11y/pull/10)
15
+ [`a578f29`](https://github.com/maerzhase/ai11y/commit/a578f296c21cd19c935ff8003442677f7a1cb72d)
16
+ Thanks [@maerzhase](https://github.com/maerzhase)! - Fix conversation history
17
+ not being passed to agent due to async state update timing issue
18
+
19
+ - [#10](https://github.com/maerzhase/ai11y/pull/10)
20
+ [`a578f29`](https://github.com/maerzhase/ai11y/commit/a578f296c21cd19c935ff8003442677f7a1cb72d)
21
+ Thanks [@maerzhase](https://github.com/maerzhase)! - Add custom state
22
+ management and privacy protection features
23
+ - **Custom State API**: `setState()` now merges with existing state (like
24
+ React's setState), allowing multiple components to independently manage
25
+ state keys. Added `clearState()` helper for resetting state.
26
+ - **Privacy Protection**: Automatically redact sensitive input values
27
+ (passwords, hidden fields, credit card fields) from UI context. Values are
28
+ replaced with `[REDACTED]` to prevent exposure to AI agents.
29
+ - **Sensitive Marker Support**: Added `data-ai-sensitive` attribute and
30
+ `sensitive` prop to Marker component for explicitly marking sensitive
31
+ fields.
32
+ - **Agent Improvements**: Enhanced agent prompts with security rules to
33
+ prevent filling password fields and improved pronoun resolution for better
34
+ context tracking in conversations.
35
+
36
+ - Updated dependencies
37
+ [[`a578f29`](https://github.com/maerzhase/ai11y/commit/a578f296c21cd19c935ff8003442677f7a1cb72d),
38
+ [`a578f29`](https://github.com/maerzhase/ai11y/commit/a578f296c21cd19c935ff8003442677f7a1cb72d)]:
39
+ - @ai11y/core@0.0.1
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # @ai11y/react
2
+
3
+ Thin React wrapper for ai11y. Uses the same **describe → plan → act** flow: wrap
4
+ your app in `Ai11yProvider`, mark elements with `Marker`, and use
5
+ `useAi11yContext()` for `describe` and `act`; call `plan()` from `@ai11y/core`.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pnpm add @ai11y/react @ai11y/core
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ Wrap your app in `Ai11yProvider` and use `Marker` so elements are registered for
16
+ `describe()`. Get `describe` and `act` from `useAi11yContext()` and call
17
+ `plan()` from `@ai11y/core`:
18
+
19
+ ```tsx
20
+ import { Ai11yProvider, Marker, useAi11yContext } from "@ai11y/react";
21
+ import { plan } from "@ai11y/core";
22
+
23
+ function App() {
24
+ return (
25
+ <Ai11yProvider onNavigate={(route) => navigate(route)}>
26
+ <YourApp />
27
+ </Ai11yProvider>
28
+ );
29
+ }
30
+
31
+ function Chat() {
32
+ const { describe, act } = useAi11yContext();
33
+ const handleSubmit = async (input: string) => {
34
+ const ui = describe();
35
+ const { reply, instructions } = await plan(ui, input);
36
+ for (const instruction of instructions ?? []) act(instruction);
37
+ return reply;
38
+ };
39
+ // ... render input and messages
40
+ }
41
+ ```
42
+
43
+ Mark elements so your agent can see them:
44
+
45
+ ```tsx
46
+ <Marker id="save_btn" label="Save" intent="Save the document">
47
+ <button onClick={onSave}>Save</button>
48
+ </Marker>
49
+ ```
50
+
51
+ For the plan step with an LLM, point the client at your server endpoint; see
52
+ [packages/agent/README.md](../agent/README.md).
@@ -0,0 +1,36 @@
1
+ import * as _ai11y_core0 from "@ai11y/core";
2
+ import { AgentConfig, Ai11yError, Ai11yEvent, Ai11yState } from "@ai11y/core";
3
+ import React from "react";
4
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
5
+
6
+ //#region src/components/Ai11yProvider.d.ts
7
+ interface Ai11yProviderContextValue {
8
+ state: Ai11yState;
9
+ currentRoute: string;
10
+ lastError: Ai11yError | null;
11
+ events: Ai11yEvent[];
12
+ onNavigate?: (route: string) => void;
13
+ track: (event: string, payload?: unknown) => void;
14
+ reportError: (error: Error, meta?: {
15
+ surface?: string;
16
+ markerId?: string;
17
+ }) => void;
18
+ describe: () => _ai11y_core0.Ai11yContext;
19
+ act: (instruction: _ai11y_core0.Instruction) => void;
20
+ agentConfig: AgentConfig | null;
21
+ }
22
+ interface Ai11yProviderProps {
23
+ children: React.ReactNode;
24
+ initialState?: Ai11yState;
25
+ onNavigate?: (route: string) => void;
26
+ agentConfig?: AgentConfig;
27
+ }
28
+ declare function Ai11yProvider({
29
+ children,
30
+ initialState,
31
+ onNavigate,
32
+ agentConfig
33
+ }: Ai11yProviderProps): react_jsx_runtime0.JSX.Element;
34
+ //#endregion
35
+ export { Ai11yProvider, Ai11yProviderContextValue };
36
+ //# sourceMappingURL=Ai11yProvider.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Ai11yProvider.d.mts","names":[],"sources":["../../src/components/Ai11yProvider.tsx"],"sourcesContent":[],"mappings":";;;;;;UAiBiB,yBAAA;SACT;;aAEI;EAHK,MAAA,EAIR,UAJQ,EAAA;EACT,UAAA,CAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EAEI,KAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,OAAA,EAAA,GAAA,IAAA;EACH,WAAA,EAAA,CAAA,KAAA,EAIA,KAJA,EAAA,IAO0C,CAP1C,EAAA;IAIA,OAAA,CAAA,EAAA,MAAA;IAAK,QAGyB,CAAA,EAAA,MAAA;EAAY,CAAA,EAAA,GAAA,IACT;EAC5B,QAAA,EAAA,GAAA,GALA,YAAA,CAGyB,YAEzB;EAAW,GAAA,EAAA,CAAA,WAAA,EAF0B,YAAA,CACT,WACjB,EAAA,GAAA,IAAA;EAOf,WAAA,EAPI,WAOc,GAAA,IAAA;;UAAlB,kBAAA,CAIK;EAAW,QAAA,EAHf,KAAA,CAAM,SAGS;EAGV,YAAA,CAAA,EALA,UAKa;EAC5B,UAAA,CAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EACA,WAAA,CAAA,EALc,WAKd;;AAEA,iBAJe,aAAA,CAIf;EAAA,QAAA;EAAA,YAAA;EAAA,UAAA;EAAA;AAAA,CAAA,EACE,kBADF,CAAA,EACoB,kBAAA,CAAA,GAAA,CAAA,OADpB"}
@@ -0,0 +1,52 @@
1
+ import { createClient, getError, getEvents, getRoute, getState, setState, subscribe, subscribeToStore } from "@ai11y/core";
2
+ import { createContext, useEffect, useRef, useState } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+
5
+ //#region src/components/Ai11yProvider.tsx
6
+ const Ai11yProviderContext = createContext(null);
7
+ function Ai11yProvider({ children, initialState = {}, onNavigate, agentConfig }) {
8
+ const clientRef = useRef(createClient({ onNavigate }));
9
+ useEffect(() => {
10
+ if (Object.keys(initialState).length > 0) setState(initialState);
11
+ }, [initialState]);
12
+ const [currentRoute, setCurrentRoute] = useState(() => getRoute() || "/");
13
+ const [uiState, setUIState] = useState(() => {
14
+ return getState() || {};
15
+ });
16
+ const [lastError, setLastError] = useState(() => {
17
+ return getError() || null;
18
+ });
19
+ const [events, setEvents] = useState(() => getEvents());
20
+ useEffect(() => {
21
+ return subscribeToStore((type, value$1) => {
22
+ if (type === "route") setCurrentRoute(value$1 || "/");
23
+ else if (type === "state") setUIState(value$1 || {});
24
+ else if (type === "error") setLastError(value$1 || null);
25
+ });
26
+ }, []);
27
+ useEffect(() => {
28
+ return subscribe(() => {
29
+ setEvents([...getEvents()]);
30
+ });
31
+ }, []);
32
+ const value = {
33
+ state: uiState,
34
+ currentRoute,
35
+ lastError,
36
+ events,
37
+ onNavigate,
38
+ track: clientRef.current.track.bind(clientRef.current),
39
+ reportError: clientRef.current.reportError.bind(clientRef.current),
40
+ describe: clientRef.current.describe.bind(clientRef.current),
41
+ act: clientRef.current.act.bind(clientRef.current),
42
+ agentConfig: agentConfig ?? null
43
+ };
44
+ return /* @__PURE__ */ jsx(Ai11yProviderContext.Provider, {
45
+ value,
46
+ children
47
+ });
48
+ }
49
+
50
+ //#endregion
51
+ export { Ai11yProvider, Ai11yProviderContext };
52
+ //# sourceMappingURL=Ai11yProvider.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Ai11yProvider.mjs","names":["value","value: Ai11yProviderContextValue"],"sources":["../../src/components/Ai11yProvider.tsx"],"sourcesContent":["import {\n\ttype AgentConfig,\n\ttype Ai11yError,\n\ttype Ai11yEvent,\n\ttype Ai11yState,\n\tcreateClient,\n\tgetError,\n\tgetEvents,\n\tgetRoute,\n\tgetState,\n\tsetState,\n\tsubscribe,\n\tsubscribeToStore,\n} from \"@ai11y/core\";\nimport type React from \"react\";\nimport { createContext, useEffect, useRef, useState } from \"react\";\n\nexport interface Ai11yProviderContextValue {\n\tstate: Ai11yState;\n\tcurrentRoute: string;\n\tlastError: Ai11yError | null;\n\tevents: Ai11yEvent[];\n\tonNavigate?: (route: string) => void;\n\ttrack: (event: string, payload?: unknown) => void;\n\treportError: (\n\t\terror: Error,\n\t\tmeta?: { surface?: string; markerId?: string },\n\t) => void;\n\tdescribe: () => import(\"@ai11y/core\").Ai11yContext;\n\tact: (instruction: import(\"@ai11y/core\").Instruction) => void;\n\tagentConfig: AgentConfig | null;\n}\n\nconst Ai11yProviderContext = createContext<Ai11yProviderContextValue | null>(\n\tnull,\n);\n\ninterface Ai11yProviderProps {\n\tchildren: React.ReactNode;\n\tinitialState?: Ai11yState;\n\tonNavigate?: (route: string) => void;\n\tagentConfig?: AgentConfig;\n}\n\nexport function Ai11yProvider({\n\tchildren,\n\tinitialState = {},\n\tonNavigate,\n\tagentConfig,\n}: Ai11yProviderProps) {\n\tconst clientRef = useRef(\n\t\tcreateClient({\n\t\t\tonNavigate,\n\t\t}),\n\t);\n\n\tuseEffect(() => {\n\t\tif (Object.keys(initialState).length > 0) {\n\t\t\tsetState(initialState);\n\t\t}\n\t}, [initialState]);\n\n\tconst [currentRoute, setCurrentRoute] = useState<string>(\n\t\t() => getRoute() || \"/\",\n\t);\n\tconst [uiState, setUIState] = useState<Ai11yState>(() => {\n\t\tconst coreState = getState();\n\t\treturn coreState || {};\n\t});\n\tconst [lastError, setLastError] = useState<Ai11yError | null>(() => {\n\t\tconst coreError = getError();\n\t\treturn coreError || null;\n\t});\n\tconst [events, setEvents] = useState<Ai11yEvent[]>(() => getEvents());\n\n\tuseEffect(() => {\n\t\tconst unsubscribe = subscribeToStore(\n\t\t\t(type: \"route\" | \"state\" | \"error\", value: unknown) => {\n\t\t\t\tif (type === \"route\") {\n\t\t\t\t\tsetCurrentRoute((value as string) || \"/\");\n\t\t\t\t} else if (type === \"state\") {\n\t\t\t\t\tsetUIState((value as Ai11yState) || {});\n\t\t\t\t} else if (type === \"error\") {\n\t\t\t\t\tsetLastError((value as Ai11yError | null) || null);\n\t\t\t\t}\n\t\t\t},\n\t\t);\n\t\treturn unsubscribe;\n\t}, []);\n\n\tuseEffect(() => {\n\t\tconst unsubscribe = subscribe(() => {\n\t\t\tsetEvents([...getEvents()]);\n\t\t});\n\t\treturn unsubscribe;\n\t}, []);\n\n\tconst value: Ai11yProviderContextValue = {\n\t\tstate: uiState,\n\t\tcurrentRoute,\n\t\tlastError,\n\t\tevents,\n\t\tonNavigate,\n\t\ttrack: clientRef.current.track.bind(clientRef.current),\n\t\treportError: clientRef.current.reportError.bind(clientRef.current),\n\t\tdescribe: clientRef.current.describe.bind(clientRef.current),\n\t\tact: clientRef.current.act.bind(clientRef.current),\n\t\tagentConfig: agentConfig ?? null,\n\t};\n\n\treturn (\n\t\t<Ai11yProviderContext.Provider value={value}>\n\t\t\t{children}\n\t\t</Ai11yProviderContext.Provider>\n\t);\n}\n\nexport { Ai11yProviderContext };\n"],"mappings":";;;;;AAiCA,MAAM,uBAAuB,cAC5B,KACA;AASD,SAAgB,cAAc,EAC7B,UACA,eAAe,EAAE,EACjB,YACA,eACsB;CACtB,MAAM,YAAY,OACjB,aAAa,EACZ,YACA,CAAC,CACF;AAED,iBAAgB;AACf,MAAI,OAAO,KAAK,aAAa,CAAC,SAAS,EACtC,UAAS,aAAa;IAErB,CAAC,aAAa,CAAC;CAElB,MAAM,CAAC,cAAc,mBAAmB,eACjC,UAAU,IAAI,IACpB;CACD,MAAM,CAAC,SAAS,cAAc,eAA2B;AAExD,SADkB,UAAU,IACR,EAAE;GACrB;CACF,MAAM,CAAC,WAAW,gBAAgB,eAAkC;AAEnE,SADkB,UAAU,IACR;GACnB;CACF,MAAM,CAAC,QAAQ,aAAa,eAA6B,WAAW,CAAC;AAErE,iBAAgB;AAYf,SAXoB,kBAClB,MAAmC,YAAmB;AACtD,OAAI,SAAS,QACZ,iBAAiBA,WAAoB,IAAI;YAC/B,SAAS,QACnB,YAAYA,WAAwB,EAAE,CAAC;YAC7B,SAAS,QACnB,cAAcA,WAA+B,KAAK;IAGpD;IAEC,EAAE,CAAC;AAEN,iBAAgB;AAIf,SAHoB,gBAAgB;AACnC,aAAU,CAAC,GAAG,WAAW,CAAC,CAAC;IAC1B;IAEA,EAAE,CAAC;CAEN,MAAMC,QAAmC;EACxC,OAAO;EACP;EACA;EACA;EACA;EACA,OAAO,UAAU,QAAQ,MAAM,KAAK,UAAU,QAAQ;EACtD,aAAa,UAAU,QAAQ,YAAY,KAAK,UAAU,QAAQ;EAClE,UAAU,UAAU,QAAQ,SAAS,KAAK,UAAU,QAAQ;EAC5D,KAAK,UAAU,QAAQ,IAAI,KAAK,UAAU,QAAQ;EAClD,aAAa,eAAe;EAC5B;AAED,QACC,oBAAC,qBAAqB;EAAgB;EACpC;GAC8B"}
@@ -0,0 +1,22 @@
1
+ import React from "react";
2
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
3
+
4
+ //#region src/components/Marker.d.ts
5
+ interface MarkerProps {
6
+ id: string;
7
+ label: string;
8
+ intent: string;
9
+ children: React.ReactElement;
10
+ /** Mark this element as containing sensitive data that should be redacted from the UI context */
11
+ sensitive?: boolean;
12
+ }
13
+ declare function Marker({
14
+ id,
15
+ label,
16
+ intent,
17
+ children,
18
+ sensitive
19
+ }: MarkerProps): react_jsx_runtime0.JSX.Element;
20
+ //#endregion
21
+ export { Marker };
22
+ //# sourceMappingURL=Marker.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Marker.d.mts","names":[],"sources":["../../src/components/Marker.tsx"],"sourcesContent":[],"mappings":";;;;UAQU,WAAA;;;EAAA,MAAA,EAAA,MAAW;EAsBL,QAAA,EAlBL,KAAA,CAAM,YAkBK;EACrB;EACA,SAAA,CAAA,EAAA,OAAA;;AAEA,iBAJe,MAAA,CAIf;EAAA,EAAA;EAAA,KAAA;EAAA,MAAA;EAAA,QAAA;EAAA;AAAA,CAAA,EAEE,WAFF,CAAA,EAEa,kBAAA,CAAA,GAAA,CAAA,OAFb"}
@@ -0,0 +1,31 @@
1
+ import { ATTRIBUTE_ID, ATTRIBUTE_INTENT, ATTRIBUTE_LABEL, ATTRIBUTE_SENSITIVE } from "@ai11y/core";
2
+ import React from "react";
3
+ import { Fragment, jsx } from "react/jsx-runtime";
4
+
5
+ //#region src/components/Marker.tsx
6
+ function assignRef(ref, value) {
7
+ if (!ref) return;
8
+ if (typeof ref === "function") {
9
+ ref(value);
10
+ return;
11
+ }
12
+ try {
13
+ ref.current = value;
14
+ } catch {}
15
+ }
16
+ function Marker({ id, label, intent, children, sensitive }) {
17
+ const existingRef = children.props.ref;
18
+ return /* @__PURE__ */ jsx(Fragment, { children: React.cloneElement(children, {
19
+ ref: (node) => {
20
+ assignRef(existingRef, node);
21
+ },
22
+ [ATTRIBUTE_ID]: id,
23
+ ...label && { [ATTRIBUTE_LABEL]: label },
24
+ ...intent && { [ATTRIBUTE_INTENT]: intent },
25
+ ...sensitive && { [ATTRIBUTE_SENSITIVE]: "true" }
26
+ }) });
27
+ }
28
+
29
+ //#endregion
30
+ export { Marker };
31
+ //# sourceMappingURL=Marker.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Marker.mjs","names":[],"sources":["../../src/components/Marker.tsx"],"sourcesContent":["import {\n\tATTRIBUTE_ID,\n\tATTRIBUTE_INTENT,\n\tATTRIBUTE_LABEL,\n\tATTRIBUTE_SENSITIVE,\n} from \"@ai11y/core\";\nimport React from \"react\";\n\ninterface MarkerProps {\n\tid: string;\n\tlabel: string;\n\tintent: string;\n\tchildren: React.ReactElement;\n\t/** Mark this element as containing sensitive data that should be redacted from the UI context */\n\tsensitive?: boolean;\n}\n\nfunction assignRef<T>(ref: React.Ref<T> | undefined, value: T | null) {\n\tif (!ref) return;\n\tif (typeof ref === \"function\") {\n\t\tref(value);\n\t\treturn;\n\t}\n\ttry {\n\t\t(ref as React.MutableRefObject<T | null>).current = value;\n\t} catch {\n\t\t// ignore read-only refs\n\t}\n}\n\nexport function Marker({\n\tid,\n\tlabel,\n\tintent,\n\tchildren,\n\tsensitive,\n}: MarkerProps) {\n\tconst existingRef = (children.props as { ref?: React.Ref<HTMLElement> }).ref;\n\tconst childWithRef = React.cloneElement(children, {\n\t\tref: (node: HTMLElement | null) => {\n\t\t\tassignRef(existingRef, node);\n\t\t},\n\t\t[ATTRIBUTE_ID]: id,\n\t\t...(label && { [ATTRIBUTE_LABEL]: label }),\n\t\t...(intent && { [ATTRIBUTE_INTENT]: intent }),\n\t\t...(sensitive && { [ATTRIBUTE_SENSITIVE]: \"true\" }),\n\t} as React.HTMLAttributes<HTMLElement> & {\n\t\t[ATTRIBUTE_ID]: string;\n\t\t[ATTRIBUTE_LABEL]?: string;\n\t\t[ATTRIBUTE_INTENT]?: string;\n\t\t[ATTRIBUTE_SENSITIVE]?: string;\n\t});\n\n\treturn <>{childWithRef}</>;\n}\n"],"mappings":";;;;;AAiBA,SAAS,UAAa,KAA+B,OAAiB;AACrE,KAAI,CAAC,IAAK;AACV,KAAI,OAAO,QAAQ,YAAY;AAC9B,MAAI,MAAM;AACV;;AAED,KAAI;AACH,EAAC,IAAyC,UAAU;SAC7C;;AAKT,SAAgB,OAAO,EACtB,IACA,OACA,QACA,UACA,aACe;CACf,MAAM,cAAe,SAAS,MAA2C;AAgBzE,QAAO,0CAfc,MAAM,aAAa,UAAU;EACjD,MAAM,SAA6B;AAClC,aAAU,aAAa,KAAK;;GAE5B,eAAe;EAChB,GAAI,SAAS,GAAG,kBAAkB,OAAO;EACzC,GAAI,UAAU,GAAG,mBAAmB,QAAQ;EAC5C,GAAI,aAAa,GAAG,sBAAsB,QAAQ;EAClD,CAKC,GAEwB"}
@@ -0,0 +1,18 @@
1
+ import { Ai11yProviderContextValue } from "../components/Ai11yProvider.mjs";
2
+
3
+ //#region src/hooks/useAi11yContext.d.ts
4
+
5
+ /**
6
+ * Hook to access the AI accessibility context
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * const ctx = useAi11yContext()
11
+ * const ui = ctx.describe()
12
+ * ctx.act({ action: "click", id: "save_button" })
13
+ * ```
14
+ */
15
+ declare function useAi11yContext(): Ai11yProviderContextValue;
16
+ //#endregion
17
+ export { useAi11yContext };
18
+ //# sourceMappingURL=useAi11yContext.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAi11yContext.d.mts","names":[],"sources":["../../src/hooks/useAi11yContext.ts"],"sourcesContent":[],"mappings":";;;;;;;AAaA;;;;;;;iBAAgB,eAAA,CAAA,GAAe"}
@@ -0,0 +1,23 @@
1
+ import { Ai11yProviderContext } from "../components/Ai11yProvider.mjs";
2
+ import { useContext } from "react";
3
+
4
+ //#region src/hooks/useAi11yContext.ts
5
+ /**
6
+ * Hook to access the AI accessibility context
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * const ctx = useAi11yContext()
11
+ * const ui = ctx.describe()
12
+ * ctx.act({ action: "click", id: "save_button" })
13
+ * ```
14
+ */
15
+ function useAi11yContext() {
16
+ const context = useContext(Ai11yProviderContext);
17
+ if (!context) throw new Error("useAi11yContext must be used within Ai11yProvider");
18
+ return context;
19
+ }
20
+
21
+ //#endregion
22
+ export { useAi11yContext };
23
+ //# sourceMappingURL=useAi11yContext.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAi11yContext.mjs","names":[],"sources":["../../src/hooks/useAi11yContext.ts"],"sourcesContent":["import { useContext } from \"react\";\nimport { Ai11yProviderContext } from \"../components/Ai11yProvider.js\";\n\n/**\n * Hook to access the AI accessibility context\n *\n * @example\n * ```tsx\n * const ctx = useAi11yContext()\n * const ui = ctx.describe()\n * ctx.act({ action: \"click\", id: \"save_button\" })\n * ```\n */\nexport function useAi11yContext() {\n\tconst context = useContext(Ai11yProviderContext);\n\tif (!context) {\n\t\tthrow new Error(\"useAi11yContext must be used within Ai11yProvider\");\n\t}\n\treturn context;\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,SAAgB,kBAAkB;CACjC,MAAM,UAAU,WAAW,qBAAqB;AAChD,KAAI,CAAC,QACJ,OAAM,IAAI,MAAM,oDAAoD;AAErE,QAAO"}
@@ -0,0 +1,31 @@
1
+ import { AgentResponse, Instruction } from "@ai11y/core";
2
+
3
+ //#region src/hooks/useChat.d.ts
4
+ interface Message {
5
+ id: string;
6
+ type: "user" | "assistant" | "system";
7
+ content: string;
8
+ timestamp: number;
9
+ }
10
+ interface UseChatOptions {
11
+ onSubmit: (message: string, messages: Message[]) => Promise<AgentResponse>;
12
+ onInstruction?: (instruction: Instruction) => void;
13
+ onMessage?: () => void;
14
+ initialMessage?: string;
15
+ }
16
+ interface UseChatReturn {
17
+ messages: Message[];
18
+ input: string;
19
+ setInput: (value: string) => void;
20
+ isProcessing: boolean;
21
+ handleSubmit: (e: React.FormEvent) => Promise<void>;
22
+ }
23
+ declare function useChat({
24
+ onSubmit,
25
+ onInstruction,
26
+ onMessage,
27
+ initialMessage
28
+ }: UseChatOptions): UseChatReturn;
29
+ //#endregion
30
+ export { useChat };
31
+ //# sourceMappingURL=useChat.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useChat.d.mts","names":[],"sources":["../../src/hooks/useChat.ts"],"sourcesContent":[],"mappings":";;;UAGiB,OAAA;;EAAA,IAAA,EAAA,MAAO,GAAA,WAAA,GAAA,QAAA;EAOP,OAAA,EAAA,MAAA;EACsB,SAAA,EAAA,MAAA;;AAAc,UADpC,cAAA,CACoC;EACtB,QAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,QAAA,EADQ,OACR,EAAA,EAAA,GADsB,OACtB,CAD8B,aAC9B,CAAA;EAAW,aAAA,CAAA,EAAA,CAAA,WAAA,EAAX,WAAW,EAAA,GAAA,IAAA;EAKzB,SAAA,CAAA,EAAA,GAAA,GAAa,IAAA;EACnB,cAAA,CAAA,EAAA,MAAA;;AAI4B,UALtB,aAAA,CAKsB;EAAO,QAAA,EAJnC,OAImC,EAAA;EAG9B,KAAA,EAAA,MAAO;EACtB,QAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EACA,YAAA,EAAA,OAAA;EACA,YAAA,EAAA,CAAA,CAAA,EANkB,KAAA,CAAM,SAMxB,EAAA,GANsC,OAMtC,CAAA,IAAA,CAAA;;AAEE,iBALa,OAAA,CAKb;EAAA,QAAA;EAAA,aAAA;EAAA,SAAA;EAAA;AAAA,CAAA,EAAA,cAAA,CAAA,EAAiB,aAAjB"}
@@ -0,0 +1,90 @@
1
+ import { useCallback, useRef, useState } from "react";
2
+
3
+ //#region src/hooks/useChat.ts
4
+ function useChat({ onSubmit, onInstruction, onMessage, initialMessage = "Hi! I'm your AI agent. I can help you navigate, click buttons, and highlight elements. Try saying 'go to billing' or 'click connect stripe'." }) {
5
+ const [messages, setMessages] = useState([{
6
+ id: "welcome",
7
+ type: "assistant",
8
+ content: initialMessage,
9
+ timestamp: Date.now()
10
+ }]);
11
+ const [input, setInput] = useState("");
12
+ const [isProcessing, setIsProcessing] = useState(false);
13
+ const processingRef = useRef(false);
14
+ const messageIdCounterRef = useRef(0);
15
+ const inputRef = useRef(input);
16
+ const isProcessingRef = useRef(isProcessing);
17
+ const messagesRef = useRef(messages);
18
+ inputRef.current = input;
19
+ isProcessingRef.current = isProcessing;
20
+ messagesRef.current = messages;
21
+ return {
22
+ messages,
23
+ input,
24
+ setInput,
25
+ isProcessing,
26
+ handleSubmit: useCallback(async (e) => {
27
+ e.preventDefault();
28
+ if (!inputRef.current.trim() || isProcessingRef.current || processingRef.current) return;
29
+ const userMessage = inputRef.current.trim();
30
+ setInput("");
31
+ setIsProcessing(true);
32
+ processingRef.current = true;
33
+ queueMicrotask(() => onMessage?.());
34
+ messageIdCounterRef.current += 1;
35
+ const userMsg = {
36
+ id: `user-${Date.now()}-${messageIdCounterRef.current}`,
37
+ type: "user",
38
+ content: userMessage,
39
+ timestamp: Date.now()
40
+ };
41
+ const updatedMessages = [...messagesRef.current, userMsg];
42
+ setMessages(updatedMessages);
43
+ queueMicrotask(() => onMessage?.());
44
+ try {
45
+ const response = await onSubmit(userMessage, updatedMessages);
46
+ if (processingRef.current) {
47
+ messageIdCounterRef.current += 1;
48
+ const assistantMsg = {
49
+ id: `assistant-${Date.now()}-${messageIdCounterRef.current}`,
50
+ type: "assistant",
51
+ content: response.reply,
52
+ timestamp: Date.now()
53
+ };
54
+ setMessages((prevMsgs) => {
55
+ if (prevMsgs.some((msg) => msg.id === assistantMsg.id || msg.type === "assistant" && msg.content === response.reply && msg.timestamp === assistantMsg.timestamp)) return prevMsgs;
56
+ return [...prevMsgs, assistantMsg];
57
+ });
58
+ queueMicrotask(() => onMessage?.());
59
+ if (response.instructions && onInstruction) for (const instruction of response.instructions) onInstruction(instruction);
60
+ }
61
+ } catch (error) {
62
+ if (processingRef.current) {
63
+ messageIdCounterRef.current += 1;
64
+ const errorMsg = {
65
+ id: `error-${Date.now()}-${messageIdCounterRef.current}`,
66
+ type: "assistant",
67
+ content: `Sorry, I encountered an error: ${error instanceof Error ? error.message : "Unknown error"}. Please try again.`,
68
+ timestamp: Date.now()
69
+ };
70
+ setMessages((prevMsgs) => {
71
+ if (prevMsgs.some((msg) => msg.id === errorMsg.id || msg.type === "assistant" && msg.content === errorMsg.content && msg.timestamp === errorMsg.timestamp)) return prevMsgs;
72
+ return [...prevMsgs, errorMsg];
73
+ });
74
+ queueMicrotask(() => onMessage?.());
75
+ }
76
+ } finally {
77
+ setIsProcessing(false);
78
+ processingRef.current = false;
79
+ }
80
+ }, [
81
+ onSubmit,
82
+ onInstruction,
83
+ onMessage
84
+ ])
85
+ };
86
+ }
87
+
88
+ //#endregion
89
+ export { useChat };
90
+ //# sourceMappingURL=useChat.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useChat.mjs","names":["userMsg: Message","assistantMsg: Message","errorMsg: Message"],"sources":["../../src/hooks/useChat.ts"],"sourcesContent":["import type { AgentResponse, Instruction } from \"@ai11y/core\";\nimport { useCallback, useRef, useState } from \"react\";\n\nexport interface Message {\n\tid: string;\n\ttype: \"user\" | \"assistant\" | \"system\";\n\tcontent: string;\n\ttimestamp: number;\n}\n\nexport interface UseChatOptions {\n\tonSubmit: (message: string, messages: Message[]) => Promise<AgentResponse>;\n\tonInstruction?: (instruction: Instruction) => void;\n\tonMessage?: () => void;\n\tinitialMessage?: string;\n}\n\nexport interface UseChatReturn {\n\tmessages: Message[];\n\tinput: string;\n\tsetInput: (value: string) => void;\n\tisProcessing: boolean;\n\thandleSubmit: (e: React.FormEvent) => Promise<void>;\n}\n\nexport function useChat({\n\tonSubmit,\n\tonInstruction,\n\tonMessage,\n\tinitialMessage = \"Hi! I'm your AI agent. I can help you navigate, click buttons, and highlight elements. Try saying 'go to billing' or 'click connect stripe'.\",\n}: UseChatOptions): UseChatReturn {\n\tconst [messages, setMessages] = useState<Message[]>([\n\t\t{\n\t\t\tid: \"welcome\",\n\t\t\ttype: \"assistant\",\n\t\t\tcontent: initialMessage,\n\t\t\ttimestamp: Date.now(),\n\t\t},\n\t]);\n\tconst [input, setInput] = useState(\"\");\n\tconst [isProcessing, setIsProcessing] = useState(false);\n\tconst processingRef = useRef(false);\n\tconst messageIdCounterRef = useRef(0);\n\tconst inputRef = useRef(input);\n\tconst isProcessingRef = useRef(isProcessing);\n\tconst messagesRef = useRef(messages);\n\tinputRef.current = input;\n\tisProcessingRef.current = isProcessing;\n\tmessagesRef.current = messages;\n\n\tconst handleSubmit = useCallback(\n\t\tasync (e: React.FormEvent) => {\n\t\t\te.preventDefault();\n\t\t\tif (\n\t\t\t\t!inputRef.current.trim() ||\n\t\t\t\tisProcessingRef.current ||\n\t\t\t\tprocessingRef.current\n\t\t\t)\n\t\t\t\treturn;\n\n\t\t\tconst userMessage = inputRef.current.trim();\n\t\t\tsetInput(\"\");\n\t\t\tsetIsProcessing(true);\n\t\t\tprocessingRef.current = true;\n\t\t\tqueueMicrotask(() => onMessage?.());\n\n\t\t\tmessageIdCounterRef.current += 1;\n\t\t\tconst userMsg: Message = {\n\t\t\t\tid: `user-${Date.now()}-${messageIdCounterRef.current}`,\n\t\t\t\ttype: \"user\",\n\t\t\t\tcontent: userMessage,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t};\n\n\t\t\tconst updatedMessages = [...messagesRef.current, userMsg];\n\t\t\tsetMessages(updatedMessages);\n\t\t\tqueueMicrotask(() => onMessage?.());\n\n\t\t\ttry {\n\t\t\t\tconst response = await onSubmit(userMessage, updatedMessages);\n\n\t\t\t\t// Add agent reply only if we're still processing (prevent duplicates)\n\t\t\t\tif (processingRef.current) {\n\t\t\t\t\tmessageIdCounterRef.current += 1;\n\t\t\t\t\tconst assistantMsg: Message = {\n\t\t\t\t\t\tid: `assistant-${Date.now()}-${messageIdCounterRef.current}`,\n\t\t\t\t\t\ttype: \"assistant\",\n\t\t\t\t\t\tcontent: response.reply,\n\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t};\n\t\t\t\t\tsetMessages((prevMsgs) => {\n\t\t\t\t\t\tconst exists = prevMsgs.some(\n\t\t\t\t\t\t\t(msg) =>\n\t\t\t\t\t\t\t\tmsg.id === assistantMsg.id ||\n\t\t\t\t\t\t\t\t(msg.type === \"assistant\" &&\n\t\t\t\t\t\t\t\t\tmsg.content === response.reply &&\n\t\t\t\t\t\t\t\t\tmsg.timestamp === assistantMsg.timestamp),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (exists) return prevMsgs;\n\t\t\t\t\t\treturn [...prevMsgs, assistantMsg];\n\t\t\t\t\t});\n\t\t\t\t\tqueueMicrotask(() => onMessage?.());\n\n\t\t\t\t\tif (response.instructions && onInstruction) {\n\t\t\t\t\t\tfor (const instruction of response.instructions) {\n\t\t\t\t\t\t\tonInstruction(instruction);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tif (processingRef.current) {\n\t\t\t\t\tmessageIdCounterRef.current += 1;\n\t\t\t\t\tconst errorMsg: Message = {\n\t\t\t\t\t\tid: `error-${Date.now()}-${messageIdCounterRef.current}`,\n\t\t\t\t\t\ttype: \"assistant\",\n\t\t\t\t\t\tcontent: `Sorry, I encountered an error: ${error instanceof Error ? error.message : \"Unknown error\"}. Please try again.`,\n\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t};\n\t\t\t\t\tsetMessages((prevMsgs) => {\n\t\t\t\t\t\tconst exists = prevMsgs.some(\n\t\t\t\t\t\t\t(msg) =>\n\t\t\t\t\t\t\t\tmsg.id === errorMsg.id ||\n\t\t\t\t\t\t\t\t(msg.type === \"assistant\" &&\n\t\t\t\t\t\t\t\t\tmsg.content === errorMsg.content &&\n\t\t\t\t\t\t\t\t\tmsg.timestamp === errorMsg.timestamp),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (exists) return prevMsgs;\n\t\t\t\t\t\treturn [...prevMsgs, errorMsg];\n\t\t\t\t\t});\n\t\t\t\t\tqueueMicrotask(() => onMessage?.());\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tsetIsProcessing(false);\n\t\t\t\tprocessingRef.current = false;\n\t\t\t}\n\t\t},\n\t\t[onSubmit, onInstruction, onMessage],\n\t);\n\n\treturn {\n\t\tmessages,\n\t\tinput,\n\t\tsetInput,\n\t\tisProcessing,\n\t\thandleSubmit,\n\t};\n}\n"],"mappings":";;;AAyBA,SAAgB,QAAQ,EACvB,UACA,eACA,WACA,iBAAiB,kJACgB;CACjC,MAAM,CAAC,UAAU,eAAe,SAAoB,CACnD;EACC,IAAI;EACJ,MAAM;EACN,SAAS;EACT,WAAW,KAAK,KAAK;EACrB,CACD,CAAC;CACF,MAAM,CAAC,OAAO,YAAY,SAAS,GAAG;CACtC,MAAM,CAAC,cAAc,mBAAmB,SAAS,MAAM;CACvD,MAAM,gBAAgB,OAAO,MAAM;CACnC,MAAM,sBAAsB,OAAO,EAAE;CACrC,MAAM,WAAW,OAAO,MAAM;CAC9B,MAAM,kBAAkB,OAAO,aAAa;CAC5C,MAAM,cAAc,OAAO,SAAS;AACpC,UAAS,UAAU;AACnB,iBAAgB,UAAU;AAC1B,aAAY,UAAU;AA2FtB,QAAO;EACN;EACA;EACA;EACA;EACA,cA9FoB,YACpB,OAAO,MAAuB;AAC7B,KAAE,gBAAgB;AAClB,OACC,CAAC,SAAS,QAAQ,MAAM,IACxB,gBAAgB,WAChB,cAAc,QAEd;GAED,MAAM,cAAc,SAAS,QAAQ,MAAM;AAC3C,YAAS,GAAG;AACZ,mBAAgB,KAAK;AACrB,iBAAc,UAAU;AACxB,wBAAqB,aAAa,CAAC;AAEnC,uBAAoB,WAAW;GAC/B,MAAMA,UAAmB;IACxB,IAAI,QAAQ,KAAK,KAAK,CAAC,GAAG,oBAAoB;IAC9C,MAAM;IACN,SAAS;IACT,WAAW,KAAK,KAAK;IACrB;GAED,MAAM,kBAAkB,CAAC,GAAG,YAAY,SAAS,QAAQ;AACzD,eAAY,gBAAgB;AAC5B,wBAAqB,aAAa,CAAC;AAEnC,OAAI;IACH,MAAM,WAAW,MAAM,SAAS,aAAa,gBAAgB;AAG7D,QAAI,cAAc,SAAS;AAC1B,yBAAoB,WAAW;KAC/B,MAAMC,eAAwB;MAC7B,IAAI,aAAa,KAAK,KAAK,CAAC,GAAG,oBAAoB;MACnD,MAAM;MACN,SAAS,SAAS;MAClB,WAAW,KAAK,KAAK;MACrB;AACD,kBAAa,aAAa;AAQzB,UAPe,SAAS,MACtB,QACA,IAAI,OAAO,aAAa,MACvB,IAAI,SAAS,eACb,IAAI,YAAY,SAAS,SACzB,IAAI,cAAc,aAAa,UACjC,CACW,QAAO;AACnB,aAAO,CAAC,GAAG,UAAU,aAAa;OACjC;AACF,0BAAqB,aAAa,CAAC;AAEnC,SAAI,SAAS,gBAAgB,cAC5B,MAAK,MAAM,eAAe,SAAS,aAClC,eAAc,YAAY;;YAIrB,OAAO;AACf,QAAI,cAAc,SAAS;AAC1B,yBAAoB,WAAW;KAC/B,MAAMC,WAAoB;MACzB,IAAI,SAAS,KAAK,KAAK,CAAC,GAAG,oBAAoB;MAC/C,MAAM;MACN,SAAS,kCAAkC,iBAAiB,QAAQ,MAAM,UAAU,gBAAgB;MACpG,WAAW,KAAK,KAAK;MACrB;AACD,kBAAa,aAAa;AAQzB,UAPe,SAAS,MACtB,QACA,IAAI,OAAO,SAAS,MACnB,IAAI,SAAS,eACb,IAAI,YAAY,SAAS,WACzB,IAAI,cAAc,SAAS,UAC7B,CACW,QAAO;AACnB,aAAO,CAAC,GAAG,UAAU,SAAS;OAC7B;AACF,0BAAqB,aAAa,CAAC;;aAE3B;AACT,oBAAgB,MAAM;AACtB,kBAAc,UAAU;;KAG1B;GAAC;GAAU;GAAe;GAAU,CACpC;EAQA"}
@@ -0,0 +1,5 @@
1
+ import { Ai11yProvider } from "./components/Ai11yProvider.mjs";
2
+ import { Marker } from "./components/Marker.mjs";
3
+ import { useAi11yContext } from "./hooks/useAi11yContext.mjs";
4
+ import { useChat } from "./hooks/useChat.mjs";
5
+ export { Ai11yProvider, Marker, useAi11yContext, useChat };
package/dist/index.mjs ADDED
@@ -0,0 +1,6 @@
1
+ import { Ai11yProvider } from "./components/Ai11yProvider.mjs";
2
+ import { Marker } from "./components/Marker.mjs";
3
+ import { useAi11yContext } from "./hooks/useAi11yContext.mjs";
4
+ import { useChat } from "./hooks/useChat.mjs";
5
+
6
+ export { Ai11yProvider, Marker, useAi11yContext, useChat };
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@ai11y/react",
3
+ "author": "maerzhase3000",
4
+ "version": "0.0.1",
5
+ "type": "module",
6
+ "main": "./dist/index.mjs",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.mts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.mts",
12
+ "import": "./dist/index.mjs",
13
+ "default": "./dist/index.mjs"
14
+ }
15
+ },
16
+ "sideEffects": false,
17
+ "dependencies": {
18
+ "@ai11y/core": "0.0.1"
19
+ },
20
+ "peerDependencies": {
21
+ "react": "19.2.3",
22
+ "react-dom": "19.2.3"
23
+ },
24
+ "devDependencies": {
25
+ "@tsconfig/create-react-app": "2.0.10",
26
+ "@types/react": "19.2.7",
27
+ "@types/react-dom": "19.2.3",
28
+ "react": "19.2.3",
29
+ "react-dom": "19.2.3",
30
+ "tsdown": "0.18.1",
31
+ "typescript": "5.9.3"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "scripts": {
37
+ "build": "tsdown",
38
+ "watch": "tsdown --watch",
39
+ "dev": "tsdown --watch"
40
+ }
41
+ }
@@ -0,0 +1,118 @@
1
+ import {
2
+ type AgentConfig,
3
+ type Ai11yError,
4
+ type Ai11yEvent,
5
+ type Ai11yState,
6
+ createClient,
7
+ getError,
8
+ getEvents,
9
+ getRoute,
10
+ getState,
11
+ setState,
12
+ subscribe,
13
+ subscribeToStore,
14
+ } from "@ai11y/core";
15
+ import type React from "react";
16
+ import { createContext, useEffect, useRef, useState } from "react";
17
+
18
+ export interface Ai11yProviderContextValue {
19
+ state: Ai11yState;
20
+ currentRoute: string;
21
+ lastError: Ai11yError | null;
22
+ events: Ai11yEvent[];
23
+ onNavigate?: (route: string) => void;
24
+ track: (event: string, payload?: unknown) => void;
25
+ reportError: (
26
+ error: Error,
27
+ meta?: { surface?: string; markerId?: string },
28
+ ) => void;
29
+ describe: () => import("@ai11y/core").Ai11yContext;
30
+ act: (instruction: import("@ai11y/core").Instruction) => void;
31
+ agentConfig: AgentConfig | null;
32
+ }
33
+
34
+ const Ai11yProviderContext = createContext<Ai11yProviderContextValue | null>(
35
+ null,
36
+ );
37
+
38
+ interface Ai11yProviderProps {
39
+ children: React.ReactNode;
40
+ initialState?: Ai11yState;
41
+ onNavigate?: (route: string) => void;
42
+ agentConfig?: AgentConfig;
43
+ }
44
+
45
+ export function Ai11yProvider({
46
+ children,
47
+ initialState = {},
48
+ onNavigate,
49
+ agentConfig,
50
+ }: Ai11yProviderProps) {
51
+ const clientRef = useRef(
52
+ createClient({
53
+ onNavigate,
54
+ }),
55
+ );
56
+
57
+ useEffect(() => {
58
+ if (Object.keys(initialState).length > 0) {
59
+ setState(initialState);
60
+ }
61
+ }, [initialState]);
62
+
63
+ const [currentRoute, setCurrentRoute] = useState<string>(
64
+ () => getRoute() || "/",
65
+ );
66
+ const [uiState, setUIState] = useState<Ai11yState>(() => {
67
+ const coreState = getState();
68
+ return coreState || {};
69
+ });
70
+ const [lastError, setLastError] = useState<Ai11yError | null>(() => {
71
+ const coreError = getError();
72
+ return coreError || null;
73
+ });
74
+ const [events, setEvents] = useState<Ai11yEvent[]>(() => getEvents());
75
+
76
+ useEffect(() => {
77
+ const unsubscribe = subscribeToStore(
78
+ (type: "route" | "state" | "error", value: unknown) => {
79
+ if (type === "route") {
80
+ setCurrentRoute((value as string) || "/");
81
+ } else if (type === "state") {
82
+ setUIState((value as Ai11yState) || {});
83
+ } else if (type === "error") {
84
+ setLastError((value as Ai11yError | null) || null);
85
+ }
86
+ },
87
+ );
88
+ return unsubscribe;
89
+ }, []);
90
+
91
+ useEffect(() => {
92
+ const unsubscribe = subscribe(() => {
93
+ setEvents([...getEvents()]);
94
+ });
95
+ return unsubscribe;
96
+ }, []);
97
+
98
+ const value: Ai11yProviderContextValue = {
99
+ state: uiState,
100
+ currentRoute,
101
+ lastError,
102
+ events,
103
+ onNavigate,
104
+ track: clientRef.current.track.bind(clientRef.current),
105
+ reportError: clientRef.current.reportError.bind(clientRef.current),
106
+ describe: clientRef.current.describe.bind(clientRef.current),
107
+ act: clientRef.current.act.bind(clientRef.current),
108
+ agentConfig: agentConfig ?? null,
109
+ };
110
+
111
+ return (
112
+ <Ai11yProviderContext.Provider value={value}>
113
+ {children}
114
+ </Ai11yProviderContext.Provider>
115
+ );
116
+ }
117
+
118
+ export { Ai11yProviderContext };
@@ -0,0 +1,55 @@
1
+ import {
2
+ ATTRIBUTE_ID,
3
+ ATTRIBUTE_INTENT,
4
+ ATTRIBUTE_LABEL,
5
+ ATTRIBUTE_SENSITIVE,
6
+ } from "@ai11y/core";
7
+ import React from "react";
8
+
9
+ interface MarkerProps {
10
+ id: string;
11
+ label: string;
12
+ intent: string;
13
+ children: React.ReactElement;
14
+ /** Mark this element as containing sensitive data that should be redacted from the UI context */
15
+ sensitive?: boolean;
16
+ }
17
+
18
+ function assignRef<T>(ref: React.Ref<T> | undefined, value: T | null) {
19
+ if (!ref) return;
20
+ if (typeof ref === "function") {
21
+ ref(value);
22
+ return;
23
+ }
24
+ try {
25
+ (ref as React.MutableRefObject<T | null>).current = value;
26
+ } catch {
27
+ // ignore read-only refs
28
+ }
29
+ }
30
+
31
+ export function Marker({
32
+ id,
33
+ label,
34
+ intent,
35
+ children,
36
+ sensitive,
37
+ }: MarkerProps) {
38
+ const existingRef = (children.props as { ref?: React.Ref<HTMLElement> }).ref;
39
+ const childWithRef = React.cloneElement(children, {
40
+ ref: (node: HTMLElement | null) => {
41
+ assignRef(existingRef, node);
42
+ },
43
+ [ATTRIBUTE_ID]: id,
44
+ ...(label && { [ATTRIBUTE_LABEL]: label }),
45
+ ...(intent && { [ATTRIBUTE_INTENT]: intent }),
46
+ ...(sensitive && { [ATTRIBUTE_SENSITIVE]: "true" }),
47
+ } as React.HTMLAttributes<HTMLElement> & {
48
+ [ATTRIBUTE_ID]: string;
49
+ [ATTRIBUTE_LABEL]?: string;
50
+ [ATTRIBUTE_INTENT]?: string;
51
+ [ATTRIBUTE_SENSITIVE]?: string;
52
+ });
53
+
54
+ return <>{childWithRef}</>;
55
+ }
@@ -0,0 +1,20 @@
1
+ import { useContext } from "react";
2
+ import { Ai11yProviderContext } from "../components/Ai11yProvider.js";
3
+
4
+ /**
5
+ * Hook to access the AI accessibility context
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * const ctx = useAi11yContext()
10
+ * const ui = ctx.describe()
11
+ * ctx.act({ action: "click", id: "save_button" })
12
+ * ```
13
+ */
14
+ export function useAi11yContext() {
15
+ const context = useContext(Ai11yProviderContext);
16
+ if (!context) {
17
+ throw new Error("useAi11yContext must be used within Ai11yProvider");
18
+ }
19
+ return context;
20
+ }
@@ -0,0 +1,147 @@
1
+ import type { AgentResponse, Instruction } from "@ai11y/core";
2
+ import { useCallback, useRef, useState } from "react";
3
+
4
+ export interface Message {
5
+ id: string;
6
+ type: "user" | "assistant" | "system";
7
+ content: string;
8
+ timestamp: number;
9
+ }
10
+
11
+ export interface UseChatOptions {
12
+ onSubmit: (message: string, messages: Message[]) => Promise<AgentResponse>;
13
+ onInstruction?: (instruction: Instruction) => void;
14
+ onMessage?: () => void;
15
+ initialMessage?: string;
16
+ }
17
+
18
+ export interface UseChatReturn {
19
+ messages: Message[];
20
+ input: string;
21
+ setInput: (value: string) => void;
22
+ isProcessing: boolean;
23
+ handleSubmit: (e: React.FormEvent) => Promise<void>;
24
+ }
25
+
26
+ export function useChat({
27
+ onSubmit,
28
+ onInstruction,
29
+ onMessage,
30
+ initialMessage = "Hi! I'm your AI agent. I can help you navigate, click buttons, and highlight elements. Try saying 'go to billing' or 'click connect stripe'.",
31
+ }: UseChatOptions): UseChatReturn {
32
+ const [messages, setMessages] = useState<Message[]>([
33
+ {
34
+ id: "welcome",
35
+ type: "assistant",
36
+ content: initialMessage,
37
+ timestamp: Date.now(),
38
+ },
39
+ ]);
40
+ const [input, setInput] = useState("");
41
+ const [isProcessing, setIsProcessing] = useState(false);
42
+ const processingRef = useRef(false);
43
+ const messageIdCounterRef = useRef(0);
44
+ const inputRef = useRef(input);
45
+ const isProcessingRef = useRef(isProcessing);
46
+ const messagesRef = useRef(messages);
47
+ inputRef.current = input;
48
+ isProcessingRef.current = isProcessing;
49
+ messagesRef.current = messages;
50
+
51
+ const handleSubmit = useCallback(
52
+ async (e: React.FormEvent) => {
53
+ e.preventDefault();
54
+ if (
55
+ !inputRef.current.trim() ||
56
+ isProcessingRef.current ||
57
+ processingRef.current
58
+ )
59
+ return;
60
+
61
+ const userMessage = inputRef.current.trim();
62
+ setInput("");
63
+ setIsProcessing(true);
64
+ processingRef.current = true;
65
+ queueMicrotask(() => onMessage?.());
66
+
67
+ messageIdCounterRef.current += 1;
68
+ const userMsg: Message = {
69
+ id: `user-${Date.now()}-${messageIdCounterRef.current}`,
70
+ type: "user",
71
+ content: userMessage,
72
+ timestamp: Date.now(),
73
+ };
74
+
75
+ const updatedMessages = [...messagesRef.current, userMsg];
76
+ setMessages(updatedMessages);
77
+ queueMicrotask(() => onMessage?.());
78
+
79
+ try {
80
+ const response = await onSubmit(userMessage, updatedMessages);
81
+
82
+ // Add agent reply only if we're still processing (prevent duplicates)
83
+ if (processingRef.current) {
84
+ messageIdCounterRef.current += 1;
85
+ const assistantMsg: Message = {
86
+ id: `assistant-${Date.now()}-${messageIdCounterRef.current}`,
87
+ type: "assistant",
88
+ content: response.reply,
89
+ timestamp: Date.now(),
90
+ };
91
+ setMessages((prevMsgs) => {
92
+ const exists = prevMsgs.some(
93
+ (msg) =>
94
+ msg.id === assistantMsg.id ||
95
+ (msg.type === "assistant" &&
96
+ msg.content === response.reply &&
97
+ msg.timestamp === assistantMsg.timestamp),
98
+ );
99
+ if (exists) return prevMsgs;
100
+ return [...prevMsgs, assistantMsg];
101
+ });
102
+ queueMicrotask(() => onMessage?.());
103
+
104
+ if (response.instructions && onInstruction) {
105
+ for (const instruction of response.instructions) {
106
+ onInstruction(instruction);
107
+ }
108
+ }
109
+ }
110
+ } catch (error) {
111
+ if (processingRef.current) {
112
+ messageIdCounterRef.current += 1;
113
+ const errorMsg: Message = {
114
+ id: `error-${Date.now()}-${messageIdCounterRef.current}`,
115
+ type: "assistant",
116
+ content: `Sorry, I encountered an error: ${error instanceof Error ? error.message : "Unknown error"}. Please try again.`,
117
+ timestamp: Date.now(),
118
+ };
119
+ setMessages((prevMsgs) => {
120
+ const exists = prevMsgs.some(
121
+ (msg) =>
122
+ msg.id === errorMsg.id ||
123
+ (msg.type === "assistant" &&
124
+ msg.content === errorMsg.content &&
125
+ msg.timestamp === errorMsg.timestamp),
126
+ );
127
+ if (exists) return prevMsgs;
128
+ return [...prevMsgs, errorMsg];
129
+ });
130
+ queueMicrotask(() => onMessage?.());
131
+ }
132
+ } finally {
133
+ setIsProcessing(false);
134
+ processingRef.current = false;
135
+ }
136
+ },
137
+ [onSubmit, onInstruction, onMessage],
138
+ );
139
+
140
+ return {
141
+ messages,
142
+ input,
143
+ setInput,
144
+ isProcessing,
145
+ handleSubmit,
146
+ };
147
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { Ai11yProvider } from "./components/Ai11yProvider.js";
2
+ export { Marker } from "./components/Marker.js";
3
+ export { useAi11yContext } from "./hooks/useAi11yContext.js";
4
+ export { useChat } from "./hooks/useChat.js";
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "nodenext",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "jsx": "react-jsx",
8
+ "declaration": true,
9
+ "declarationMap": true,
10
+ "sourceMap": true,
11
+ "outDir": "dist",
12
+ "rootDir": "src",
13
+ "strict": true,
14
+ "esModuleInterop": true,
15
+ "skipLibCheck": true,
16
+ "forceConsistentCasingInFileNames": true,
17
+ "noImplicitReturns": true,
18
+ "noUnusedLocals": false
19
+ },
20
+ "include": ["src/**/*"],
21
+ "exclude": ["node_modules", "dist"]
22
+ }
@@ -0,0 +1,17 @@
1
+ import { defineConfig } from "tsdown";
2
+
3
+ export default defineConfig({
4
+ entry: ["src/index.ts"],
5
+ unbundle: true,
6
+ format: ["esm"],
7
+ dts: true,
8
+ clean: true,
9
+ outDir: "dist",
10
+ external: [
11
+ "react",
12
+ "react-dom",
13
+ "react/jsx-runtime",
14
+ "@ai11y/ui",
15
+ "@ai11y/core",
16
+ ],
17
+ });