@alexkroman1/aai-ui 0.12.3 → 0.13.0
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.
- package/README.md +257 -0
- package/dist/_colors-CpZO-88A.js +78 -0
- package/dist/_module-url-C13kAJ87.js +15 -0
- package/dist/_page-shell.d.ts +11 -0
- package/dist/_recover-run.d.ts +79 -0
- package/dist/_repeat-until.d.ts +30 -0
- package/dist/_run-controls.d.ts +33 -0
- package/dist/_submission-state.d.ts +92 -0
- package/dist/_tap-to-talk-state.d.ts +88 -0
- package/dist/_utils-Mi-SCFA1.js +47 -0
- package/dist/_utils.d.ts +26 -0
- package/dist/_web-storage.d.ts +43 -0
- package/dist/_workflow-api-ref.d.ts +37 -0
- package/dist/_workflow-files.d.ts +34 -0
- package/dist/_workflow-test-defs.d.ts +38 -0
- package/dist/aai-logo-D8pMVn9_.js +85 -0
- package/dist/agent-state-labels.d.ts +65 -0
- package/dist/agent-state.d.ts +108 -0
- package/dist/audio/capture.d.ts +44 -0
- package/dist/audio/index.d.ts +18 -0
- package/dist/audio/index.js +431 -0
- package/dist/audio/preconnect.d.ts +87 -0
- package/dist/audio/voice-io.d.ts +112 -0
- package/dist/build-default-client.d.ts +1 -0
- package/dist/chat-view-BCbI7gD4.js +396 -0
- package/dist/client-config-C7fQrioM.js +139 -0
- package/dist/client-config.d.ts +119 -0
- package/dist/client-dir.d.ts +44 -0
- package/dist/client-dir.js +58 -0
- package/dist/client-identity.d.ts +131 -0
- package/dist/client-tool.d.ts +51 -0
- package/dist/components/_colors.d.ts +73 -0
- package/dist/components/_form-readiness.d.ts +56 -0
- package/dist/components/_form-values.d.ts +19 -0
- package/dist/components/aai-logo.d.ts +12 -0
- package/dist/components/audio-result.d.ts +94 -0
- package/dist/components/auto-scroll.d.ts +74 -0
- package/dist/components/bullet-list.d.ts +74 -0
- package/dist/components/button.d.ts +29 -21
- package/dist/components/button.js +62 -23
- package/dist/components/chat-view.d.ts +24 -18
- package/dist/components/chat-view.js +2 -60
- package/dist/components/console-shell.d.ts +85 -0
- package/dist/components/controls.d.ts +21 -6
- package/dist/components/controls.js +31 -19
- package/dist/components/conversation-view.d.ts +130 -0
- package/dist/components/eyebrow.d.ts +12 -0
- package/dist/components/facts.d.ts +81 -0
- package/dist/components/form-fields.d.ts +142 -0
- package/dist/components/form-types.d.ts +73 -0
- package/dist/components/form.d.ts +96 -0
- package/dist/components/markdown.d.ts +57 -0
- package/dist/components/message-list.d.ts +34 -7
- package/dist/components/message-list.js +2 -75
- package/dist/components/session-controls.d.ts +125 -0
- package/dist/components/session-error-banner.d.ts +69 -0
- package/dist/components/session-state-dot.d.ts +111 -0
- package/dist/components/sidebar-layout.d.ts +31 -10
- package/dist/components/sidebar-layout.js +32 -12
- package/dist/components/start-screen.d.ts +17 -6
- package/dist/components/start-screen.js +34 -14
- package/dist/components/tool-call-block.d.ts +15 -14
- package/dist/components/tool-call-block.js +2 -117
- package/dist/components/tool-call-row.d.ts +75 -0
- package/dist/components/tool-config-context.d.ts +22 -0
- package/dist/components/upload-progress.d.ts +76 -0
- package/dist/components/url-chips.d.ts +29 -0
- package/dist/components/workflow-fields.d.ts +46 -0
- package/dist/components/workflow-pending-note.d.ts +81 -0
- package/dist/components/workflow-progress.d.ts +81 -0
- package/dist/components/workflow-run-error.d.ts +48 -0
- package/dist/components/workflow-run-panel.d.ts +79 -0
- package/dist/context.d.ts +288 -0
- package/dist/context.js +382 -0
- package/dist/conversation-log.d.ts +88 -0
- package/dist/default-client/assets/_module-url-BX0RuRU2.js +1 -0
- package/dist/default-client/assets/audio-cGFGID-2.js +1 -0
- package/dist/default-client/assets/capture-processor-BUABoo3_.js +90 -0
- package/dist/default-client/assets/index-8ihb1Urm.css +2 -0
- package/dist/default-client/assets/index-BQURVARl.js +126 -0
- package/dist/default-client/assets/playback-processor-ABOa11j0.js +314 -0
- package/dist/default-client/favicon.ico +0 -0
- package/dist/default-client/index.html +15 -0
- package/dist/default-client.d.ts +23 -0
- package/dist/define-client.d.ts +162 -26
- package/dist/define-client.js +201 -55
- package/dist/eyebrow-UfmSz9yy.js +27 -0
- package/dist/hooks-DN4gHYsj.js +182 -0
- package/dist/hooks.d.ts +172 -0
- package/dist/hooks.js +3 -0
- package/dist/hooks.test-d.d.ts +14 -0
- package/dist/inbox-protocol.d.ts +128 -0
- package/dist/inbox.d.ts +132 -0
- package/dist/index.d.ts +126 -29
- package/dist/index.js +5500 -15
- package/dist/internal.d.ts +43 -0
- package/dist/internal.js +8 -0
- package/dist/linked-client.d.ts +70 -0
- package/dist/message-list-DHZcLhRA.js +682 -0
- package/dist/notice-player.d.ts +70 -0
- package/dist/page.d.ts +114 -0
- package/dist/phone.d.ts +50 -0
- package/dist/route-fetch.d.ts +79 -0
- package/dist/session/audio-effects.d.ts +31 -0
- package/dist/session/audio-setup.d.ts +57 -0
- package/dist/session/audio-state.d.ts +157 -0
- package/dist/session/browser-session.d.ts +30 -0
- package/dist/session/close.d.ts +26 -0
- package/dist/session/dial.d.ts +65 -0
- package/dist/session/handshake.d.ts +60 -0
- package/dist/session/index.d.ts +17 -0
- package/dist/session/index.js +2 -0
- package/dist/session/messages.d.ts +63 -0
- package/dist/session/mic.d.ts +64 -0
- package/dist/session/preconnect.d.ts +48 -0
- package/dist/session/reconnect.d.ts +25 -0
- package/dist/session/resume-store.d.ts +57 -0
- package/dist/session/state.d.ts +146 -0
- package/dist/session/ticket.d.ts +44 -0
- package/dist/session/types.d.ts +476 -0
- package/dist/session/url.d.ts +17 -0
- package/dist/session/user-turn.d.ts +55 -0
- package/dist/session-CE0Ipsb3.js +2129 -0
- package/dist/stored-value.d.ts +103 -0
- package/dist/tool-call-block-uXtMOUWN.js +204 -0
- package/dist/tool-config-context-Es4YUzV2.js +19 -0
- package/dist/types.d.ts +272 -34
- package/dist/types.js +35 -4
- package/dist/upload/files.d.ts +83 -0
- package/dist/upload/index.d.ts +15 -0
- package/dist/upload/pause.d.ts +31 -0
- package/dist/upload/recall.d.ts +67 -0
- package/dist/upload/report.d.ts +26 -0
- package/dist/upload/session.d.ts +99 -0
- package/dist/url-chips-oyy_Eiqh.js +239 -0
- package/dist/use-client-runs.d.ts +87 -0
- package/dist/use-conversation-log.d.ts +86 -0
- package/dist/use-conversation.d.ts +131 -0
- package/dist/use-copy.d.ts +74 -0
- package/dist/use-download-url.d.ts +84 -0
- package/dist/use-flash.d.ts +61 -0
- package/dist/use-inbox.d.ts +101 -0
- package/dist/use-push-to-talk.d.ts +105 -0
- package/dist/use-route-mutation.d.ts +113 -0
- package/dist/use-route.d.ts +82 -0
- package/dist/use-run-key.d.ts +133 -0
- package/dist/use-session-controls.d.ts +69 -0
- package/dist/use-session-id.d.ts +57 -0
- package/dist/use-tap-to-talk.d.ts +138 -0
- package/dist/use-user-transcript-uyHhzy4d.js +73 -0
- package/dist/use-user-transcript.d.ts +75 -0
- package/dist/use-workflow-form.d.ts +285 -0
- package/dist/use-workflow-progress.d.ts +137 -0
- package/dist/use-workflow-run-DdhWmbx-.js +697 -0
- package/dist/use-workflow-run.d.ts +100 -0
- package/dist/use-workflow-runs.d.ts +71 -0
- package/dist/use-workflow-stream.d.ts +154 -0
- package/dist/use-workflows.d.ts +77 -0
- package/dist/workflow-client.d.ts +136 -0
- package/dist/workflow-def-types.d.ts +31 -0
- package/dist/workflow-events.d.ts +39 -0
- package/dist/workflow-field-kind.d.ts +95 -0
- package/dist/workflow-status-labels.d.ts +35 -0
- package/dist/worklets/_module-url.d.ts +10 -0
- package/dist/worklets/_playback-bench-harness.d.ts +181 -0
- package/dist/worklets/_playback-bench-host.d.ts +63 -0
- package/dist/worklets/_playback-bench-page.d.ts +65 -0
- package/dist/worklets/_tts-trace-harness.d.ts +142 -0
- package/dist/worklets/_worklet-test-utils.d.ts +27 -0
- package/dist/worklets/capture-processor.d.ts +4 -2
- package/dist/worklets/capture-processor.js +73 -33
- package/dist/worklets/playback-processor.d.ts +4 -2
- package/dist/worklets/playback-processor.js +258 -42
- package/package.json +57 -32
- package/styles.css +152 -30
- package/dist/audio.d.ts +0 -45
- package/dist/audio.js +0 -121
- package/dist/client-context.d.ts +0 -33
- package/dist/client-context.js +0 -15
- package/dist/client-handler.d.ts +0 -43
- package/dist/components/app.d.ts +0 -23
- package/dist/components/app.js +0 -41
- package/dist/components/error-banner.d.ts +0 -21
- package/dist/components/error-banner.js +0 -27
- package/dist/components/message-bubble.d.ts +0 -23
- package/dist/components/message-bubble.js +0 -35
- package/dist/components/state-indicator.d.ts +0 -29
- package/dist/components/state-indicator.js +0 -37
- package/dist/components/thinking-indicator.d.ts +0 -16
- package/dist/components/thinking-indicator.js +0 -34
- package/dist/components/tool-icons.d.ts +0 -17
- package/dist/components/tool-icons.js +0 -128
- package/dist/components/transcript.d.ts +0 -25
- package/dist/components/transcript.js +0 -35
- package/dist/session-CWB7vmqz.js +0 -429
- package/dist/session.d.ts +0 -69
- package/dist/session.js +0 -2
- package/dist/signals.d.ts +0 -120
- package/dist/signals.js +0 -161
- package/dist/tsdown.config.d.ts +0 -2
- package/dist/types.test-d.d.ts +0 -7
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { BrowserSession, SendTextOptions, SessionSnapshot } from "./session/index.ts";
|
|
3
|
+
import type { AgentState, ClientTheme, SessionError } from "./types.ts";
|
|
4
|
+
/**
|
|
5
|
+
* Provides the {@link BrowserSession} the session hooks read. `mountClient()`
|
|
6
|
+
* installs it automatically; a custom tree only needs it when bypassing
|
|
7
|
+
* `mountClient()` and mounting React itself.
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export declare function SessionProvider({ value, children, }: {
|
|
12
|
+
value: BrowserSession;
|
|
13
|
+
children?: ReactNode;
|
|
14
|
+
}): import("react").FunctionComponentElement<import("react").ProviderProps<BrowserSession | null>>;
|
|
15
|
+
/**
|
|
16
|
+
* The session's control methods, and nothing else — what a `client.tsx` may
|
|
17
|
+
* legitimately CALL on a session, as against what it may read.
|
|
18
|
+
*
|
|
19
|
+
* Declared once and merged into {@link Session} rather than written out at both
|
|
20
|
+
* places: the two lists have to be the same list, and a member added to one and
|
|
21
|
+
* not the other is a hook that cannot do what `useSession()` can.
|
|
22
|
+
*
|
|
23
|
+
* DECLARED here rather than picked out of {@link BrowserSession}: that handle is
|
|
24
|
+
* sealed and grows, and a `Pick` made every member it gained a candidate for
|
|
25
|
+
* this list without anyone deciding. Push-to-talk is the case in point — its
|
|
26
|
+
* three edges are `session.userTurn`, reached through `usePushToTalk`, and not
|
|
27
|
+
* actions every chrome is handed. `createBrowserSession` is what keeps each of
|
|
28
|
+
* these assignable from the session's own methods.
|
|
29
|
+
*
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
export type SessionActions = {
|
|
33
|
+
/** Start the call for the first time — see {@link BrowserSession.start}. */
|
|
34
|
+
start(): void;
|
|
35
|
+
/** Cancel the current agent turn and discard in-flight TTS audio. */
|
|
36
|
+
cancel(): void;
|
|
37
|
+
/** Send a typed user turn, answered as if spoken — see {@link BrowserSession.sendText}. */
|
|
38
|
+
sendText(text: string, options?: SendTextOptions): void;
|
|
39
|
+
/** Mute or unmute the mic without dropping the session — see {@link BrowserSession.setMicMuted}. */
|
|
40
|
+
setMicMuted(muted: boolean): void;
|
|
41
|
+
/** Clear messages, transcripts and error state, keeping the connection. */
|
|
42
|
+
resetState(): void;
|
|
43
|
+
/** Clear state and reopen the connection — the same session id. */
|
|
44
|
+
reset(): void;
|
|
45
|
+
/** End the call and begin a fresh one — see {@link BrowserSession.restart}. */
|
|
46
|
+
restart(): void;
|
|
47
|
+
/** Continue an earlier session by its id — see {@link BrowserSession.resume}. */
|
|
48
|
+
resume(sessionId: string): void;
|
|
49
|
+
/** Close the WebSocket and release all audio resources. */
|
|
50
|
+
disconnect(): void;
|
|
51
|
+
/** Toggle between connected and disconnected (after `start()`). */
|
|
52
|
+
toggle(): void;
|
|
53
|
+
/** End the call and return to the not-started state — see {@link BrowserSession.end}. */
|
|
54
|
+
end(): void;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* What {@link useSession} returns: the live {@link SessionSnapshot} fields
|
|
58
|
+
* (`state`, `messages`, `toolCalls`, `agentState`, live transcripts, `error`,
|
|
59
|
+
* `apiUrl`, `started`/`running`/`recording`, …) merged with the session's
|
|
60
|
+
* control methods (`start`, `toggle`, `reset`, `restart`, `resume`, `resetState`,
|
|
61
|
+
* `disconnect`, `cancel`, `end`, `sendText`, `setMicMuted`).
|
|
62
|
+
*
|
|
63
|
+
* `sendText(text)` is the one input besides the microphone: a typed turn the
|
|
64
|
+
* agent answers aloud, as if it had been spoken.
|
|
65
|
+
*
|
|
66
|
+
* @public
|
|
67
|
+
*/
|
|
68
|
+
export type Session = SessionSnapshot & SessionActions;
|
|
69
|
+
/**
|
|
70
|
+
* Return the raw {@link BrowserSession} from context without subscribing to
|
|
71
|
+
* snapshot changes. Useful for accessing stable methods (`start`, `toggle`,
|
|
72
|
+
* `reset`, …) from components that select narrow state via
|
|
73
|
+
* {@link useSessionSelector}.
|
|
74
|
+
*
|
|
75
|
+
* Not part of the package's public export surface — internal to aai-ui
|
|
76
|
+
* components.
|
|
77
|
+
*/
|
|
78
|
+
export declare function useSessionCore(): BrowserSession;
|
|
79
|
+
/** The session from context, or `null` outside the provider (`useRoute` on a `mountPage()` page). */
|
|
80
|
+
export declare function useOptionalSessionCore(): BrowserSession | null;
|
|
81
|
+
/**
|
|
82
|
+
* The session's control methods — `start`, `cancel`, `sendText`,
|
|
83
|
+
* `setMicMuted`, `resetState`, `reset`, `restart`, `resume`, `disconnect`,
|
|
84
|
+
* `toggle`, `end` — with **no snapshot subscription**.
|
|
85
|
+
* Push-to-talk is not among them: that is `usePushToTalk`.
|
|
86
|
+
*
|
|
87
|
+
* This is the narrow half of {@link useSession}, and it is the half a custom
|
|
88
|
+
* chrome could not reach. `<Controls>` and `<StartScreen>` in this package pair
|
|
89
|
+
* a one-field `useSessionSelector` with this package's own `useSessionCore`
|
|
90
|
+
* (`context.ts`, unpublished); a `client.tsx`
|
|
91
|
+
* could not, because that hook is not published — so a footer needing `start`
|
|
92
|
+
* and `toggle` held a WHOLE-SNAPSHOT `useSession()`, and `session/browser-session.ts`
|
|
93
|
+
* rebuilds the snapshot object on every change. Measured consequence: four
|
|
94
|
+
* components across three templates re-rendered on every STT partial and every
|
|
95
|
+
* streaming delta, in files whose every other component is narrowly subscribed
|
|
96
|
+
* on purpose. One of them (`text-adventure-agent`'s `TitleScreen`) reads nothing
|
|
97
|
+
* from the snapshot at all and subscribes to all of it for `session.start`.
|
|
98
|
+
*
|
|
99
|
+
* **Why publishing this does not reopen what `/internal` closed.**
|
|
100
|
+
* `useSessionCore` hands back the STORE — `subscribe`, `getSnapshot`,
|
|
101
|
+
* `connect`, `Symbol.dispose` — which is the framework's own plumbing, the same
|
|
102
|
+
* category as the providers and `buildAgentUrl` that live on
|
|
103
|
+
* `@alexkroman1/aai-ui/internal`. A client that holds it can subscribe out of
|
|
104
|
+
* band of React, dial a socket the mount did not, and dispose the session under
|
|
105
|
+
* the tree that is rendering it. What comes back from here is the SAME
|
|
106
|
+
* methods `useSession()` already publishes on its result, built into a fresh
|
|
107
|
+
* object rather than passed through, so the store is not reachable from it.
|
|
108
|
+
* There is no new capability here — only the existing one without the
|
|
109
|
+
* subscription tax.
|
|
110
|
+
*
|
|
111
|
+
* Identity-stable per core, so it is safe in a dependency array and in a
|
|
112
|
+
* `memo()` child's props: the methods are closures created once by
|
|
113
|
+
* `createBrowserSession`, and the object wrapping them is memoized on the core.
|
|
114
|
+
*
|
|
115
|
+
* Throws outside the provider `mountClient()` installs, like every session hook.
|
|
116
|
+
*
|
|
117
|
+
* @example A footer that acts on the session without re-rendering with it
|
|
118
|
+
* ```tsx
|
|
119
|
+
* import { useSessionActions, useSessionSelector } from "@alexkroman1/aai-ui";
|
|
120
|
+
*
|
|
121
|
+
* function Footer() {
|
|
122
|
+
* // Two narrow subscriptions and no snapshot read: this row re-renders when
|
|
123
|
+
* // `running` flips, and not on every transcript delta.
|
|
124
|
+
* const running = useSessionSelector((s) => s.running);
|
|
125
|
+
* const { toggle, end } = useSessionActions();
|
|
126
|
+
* return (
|
|
127
|
+
* <>
|
|
128
|
+
* <button onClick={toggle}>{running ? "Pause" : "Resume"}</button>
|
|
129
|
+
* <button onClick={end}>Hang up</button>
|
|
130
|
+
* </>
|
|
131
|
+
* );
|
|
132
|
+
* }
|
|
133
|
+
* ```
|
|
134
|
+
*
|
|
135
|
+
* @returns The control methods — see {@link SessionActions}.
|
|
136
|
+
*
|
|
137
|
+
* @public
|
|
138
|
+
*/
|
|
139
|
+
export declare function useSessionActions(): SessionActions;
|
|
140
|
+
/**
|
|
141
|
+
* Return the live {@link Session}: the current snapshot fields plus the
|
|
142
|
+
* control methods (`start`, `toggle`, `reset`, `resetState`, `disconnect`,
|
|
143
|
+
* `cancel`, `end`).
|
|
144
|
+
*
|
|
145
|
+
* Throws if used outside the provider `mountClient()` installs (the error names
|
|
146
|
+
* `<SessionProvider>` — you only mount that yourself when bypassing
|
|
147
|
+
* `mountClient()`). Re-renders the component on *every* snapshot change; for a
|
|
148
|
+
* component that reads one field, prefer {@link useSessionSelector} for a
|
|
149
|
+
* targeted subscription.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```tsx
|
|
153
|
+
* import { useSession } from "@alexkroman1/aai-ui";
|
|
154
|
+
*
|
|
155
|
+
* function Controls() {
|
|
156
|
+
* const session = useSession();
|
|
157
|
+
* if (!session.started) return <button onClick={session.start}>Start</button>;
|
|
158
|
+
* return <button onClick={session.toggle}>{session.running ? "Pause" : "Resume"}</button>;
|
|
159
|
+
* }
|
|
160
|
+
* ```
|
|
161
|
+
*
|
|
162
|
+
* @public
|
|
163
|
+
*/
|
|
164
|
+
export declare function useSession(): Session;
|
|
165
|
+
/**
|
|
166
|
+
* Subscribe to a narrow slice of the session snapshot.
|
|
167
|
+
*
|
|
168
|
+
* Unlike {@link useSession} — which re-renders the component on *every*
|
|
169
|
+
* snapshot change — this only triggers a re-render when the selected value
|
|
170
|
+
* changes (per `isEqual`, default `Object.is`). Use it for components that
|
|
171
|
+
* read a single field, e.g. `useSessionSelector((s) => s.running)`.
|
|
172
|
+
*
|
|
173
|
+
* The selector must be pure. It may run on every snapshot change, so keep it
|
|
174
|
+
* cheap; when it returns a derived object, pass a custom `isEqual` to avoid
|
|
175
|
+
* re-renders on referentially-new-but-equal results.
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* ```tsx
|
|
179
|
+
* import { useSessionSelector } from "@alexkroman1/aai-ui";
|
|
180
|
+
*
|
|
181
|
+
* // Re-renders when `running` flips, and on nothing else — not on every
|
|
182
|
+
* // transcript delta the way `useSession()` would.
|
|
183
|
+
* function MicDot() {
|
|
184
|
+
* const running = useSessionSelector((snapshot) => snapshot.running);
|
|
185
|
+
* return <span>{running ? "●" : "○"}</span>;
|
|
186
|
+
* }
|
|
187
|
+
* ```
|
|
188
|
+
*
|
|
189
|
+
* @param selector - Reads the slice out of the snapshot. Must be pure.
|
|
190
|
+
* @param isEqual - Compares two selected values. Defaults to `Object.is`.
|
|
191
|
+
* @returns The selected slice.
|
|
192
|
+
*
|
|
193
|
+
* @public
|
|
194
|
+
*/
|
|
195
|
+
export declare function useSessionSelector<T>(selector: (snapshot: SessionSnapshot) => T, isEqual?: (a: T, b: T) => boolean): T;
|
|
196
|
+
/**
|
|
197
|
+
* The agent's live {@link AgentState} — `disconnected`, `connecting`, `ready`,
|
|
198
|
+
* `listening`, `thinking`, `speaking`, `error` — on its own narrow
|
|
199
|
+
* subscription.
|
|
200
|
+
*
|
|
201
|
+
* `useSessionSelector((s) => s.state)` spelled once. It is one of exactly two
|
|
202
|
+
* snapshot fields that more than one custom chrome ever selects (the other is
|
|
203
|
+
* {@link useSessionError}), and it had been written inline at eight sites —
|
|
204
|
+
* including inside this package and, worse, in `ConsoleShell`'s own `@example`,
|
|
205
|
+
* which taught the inline form to everyone who read it.
|
|
206
|
+
*
|
|
207
|
+
* **Named `useSessionStatus`, not `useSessionState`.** `useAgentState` is the
|
|
208
|
+
* SLOT hook — the agent's own synced application state, whatever a
|
|
209
|
+
* `sessionSlot()` projects — and `AgentState` here is the phase of the CALL.
|
|
210
|
+
* Two different concepts one letter apart, so the shorter-sounding name is the
|
|
211
|
+
* one deliberately not taken.
|
|
212
|
+
*
|
|
213
|
+
* Pair it with {@link AGENT_STATE_LABELS} for a rendered word; the raw member
|
|
214
|
+
* is a wire value, not a label.
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```tsx
|
|
218
|
+
* import { AGENT_STATE_LABELS, useSessionStatus } from "@alexkroman1/aai-ui";
|
|
219
|
+
*
|
|
220
|
+
* function StatusDot() {
|
|
221
|
+
* const status = useSessionStatus();
|
|
222
|
+
* return <span data-state={status}>{AGENT_STATE_LABELS[status]}</span>;
|
|
223
|
+
* }
|
|
224
|
+
* ```
|
|
225
|
+
*
|
|
226
|
+
* @returns The current agent state.
|
|
227
|
+
*
|
|
228
|
+
* @public
|
|
229
|
+
*/
|
|
230
|
+
export declare function useSessionStatus(): AgentState;
|
|
231
|
+
/**
|
|
232
|
+
* The session's current {@link SessionError}, or `null` when there is none, on
|
|
233
|
+
* its own narrow subscription.
|
|
234
|
+
*
|
|
235
|
+
* The other half of {@link useSessionStatus} — the second of the two fields a
|
|
236
|
+
* custom chrome reads over and over, and the one whose absence is invisible:
|
|
237
|
+
* per the `fatalError` latch in `session/browser-session.ts` the error is the ONLY
|
|
238
|
+
* remaining signal that a session died, since the state beside it goes back to
|
|
239
|
+
* reading like a live one.
|
|
240
|
+
*
|
|
241
|
+
* A chrome rendering it owes `role="alert"` — which is what
|
|
242
|
+
* {@link SessionErrorBanner} is for, and why reaching for that beats reaching
|
|
243
|
+
* for this.
|
|
244
|
+
*
|
|
245
|
+
* @returns The current error, or `null`.
|
|
246
|
+
*
|
|
247
|
+
* @public
|
|
248
|
+
*/
|
|
249
|
+
export declare function useSessionError(): SessionError | null;
|
|
250
|
+
/**
|
|
251
|
+
* Provides the theme the components read via `useTheme`. `mountClient()` installs
|
|
252
|
+
* it automatically (from `ClientConfig.theme`); a custom tree only needs it
|
|
253
|
+
* when bypassing `mountClient()` and mounting React itself.
|
|
254
|
+
*
|
|
255
|
+
* @internal
|
|
256
|
+
*/
|
|
257
|
+
export declare function ThemeProvider({ value, children, }: {
|
|
258
|
+
value?: ClientTheme | undefined;
|
|
259
|
+
children?: ReactNode;
|
|
260
|
+
}): import("react").FunctionComponentElement<import("react").ProviderProps<Required<ClientTheme>>>;
|
|
261
|
+
/**
|
|
262
|
+
* Read the resolved theme (every {@link ClientTheme} field filled with its
|
|
263
|
+
* default) from the nearest theme context. Returns the default theme when no
|
|
264
|
+
* provider is present, so components can call it unconditionally.
|
|
265
|
+
*
|
|
266
|
+
* This is how a custom component stays on the agent's palette: a
|
|
267
|
+
* `mountClient({ theme })` override reaches it here, where a hardcoded colour or a
|
|
268
|
+
* Tailwind class cannot see it.
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* ```tsx
|
|
272
|
+
* import { useTheme } from "@alexkroman1/aai-ui";
|
|
273
|
+
*
|
|
274
|
+
* function Total({ amount }: { amount: string }) {
|
|
275
|
+
* const theme = useTheme();
|
|
276
|
+
* return (
|
|
277
|
+
* <strong style={{ color: theme.primary, background: theme.surface }}>
|
|
278
|
+
* {amount}
|
|
279
|
+
* </strong>
|
|
280
|
+
* );
|
|
281
|
+
* }
|
|
282
|
+
* ```
|
|
283
|
+
*
|
|
284
|
+
* @returns Every {@link ClientTheme} field, filled in.
|
|
285
|
+
*
|
|
286
|
+
* @public
|
|
287
|
+
*/
|
|
288
|
+
export declare function useTheme(): Required<ClientTheme>;
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import { createContext, createElement, useContext, useEffect, useMemo, useSyncExternalStore } from "react";
|
|
2
|
+
import { useSyncExternalStoreWithSelector } from "use-sync-external-store/with-selector";
|
|
3
|
+
//#region src/context.ts
|
|
4
|
+
const DEFAULT_THEME = {
|
|
5
|
+
bg: "#FBF8F2",
|
|
6
|
+
primary: "#3F2BC1",
|
|
7
|
+
text: "#1B1A18",
|
|
8
|
+
surface: "#FFFFFF",
|
|
9
|
+
border: "#DCD7CC"
|
|
10
|
+
};
|
|
11
|
+
const SessionCtx = createContext(null);
|
|
12
|
+
/**
|
|
13
|
+
* Provides the {@link BrowserSession} the session hooks read. `mountClient()`
|
|
14
|
+
* installs it automatically; a custom tree only needs it when bypassing
|
|
15
|
+
* `mountClient()` and mounting React itself.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
function SessionProvider({ value, children }) {
|
|
20
|
+
return createElement(SessionCtx.Provider, { value }, children);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Return the raw {@link BrowserSession} from context without subscribing to
|
|
24
|
+
* snapshot changes. Useful for accessing stable methods (`start`, `toggle`,
|
|
25
|
+
* `reset`, …) from components that select narrow state via
|
|
26
|
+
* {@link useSessionSelector}.
|
|
27
|
+
*
|
|
28
|
+
* Not part of the package's public export surface — internal to aai-ui
|
|
29
|
+
* components.
|
|
30
|
+
*/
|
|
31
|
+
function useSessionCore() {
|
|
32
|
+
const core = useContext(SessionCtx);
|
|
33
|
+
if (!core) throw new Error("Session hooks must be used within <SessionProvider>");
|
|
34
|
+
return core;
|
|
35
|
+
}
|
|
36
|
+
/** The session from context, or `null` outside the provider (`useRoute` on a `mountPage()` page). */
|
|
37
|
+
function useOptionalSessionCore() {
|
|
38
|
+
return useContext(SessionCtx);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The session's control methods — `start`, `cancel`, `sendText`,
|
|
42
|
+
* `setMicMuted`, `resetState`, `reset`, `restart`, `resume`, `disconnect`,
|
|
43
|
+
* `toggle`, `end` — with **no snapshot subscription**.
|
|
44
|
+
* Push-to-talk is not among them: that is `usePushToTalk`.
|
|
45
|
+
*
|
|
46
|
+
* This is the narrow half of {@link useSession}, and it is the half a custom
|
|
47
|
+
* chrome could not reach. `<Controls>` and `<StartScreen>` in this package pair
|
|
48
|
+
* a one-field `useSessionSelector` with this package's own `useSessionCore`
|
|
49
|
+
* (`context.ts`, unpublished); a `client.tsx`
|
|
50
|
+
* could not, because that hook is not published — so a footer needing `start`
|
|
51
|
+
* and `toggle` held a WHOLE-SNAPSHOT `useSession()`, and `session/browser-session.ts`
|
|
52
|
+
* rebuilds the snapshot object on every change. Measured consequence: four
|
|
53
|
+
* components across three templates re-rendered on every STT partial and every
|
|
54
|
+
* streaming delta, in files whose every other component is narrowly subscribed
|
|
55
|
+
* on purpose. One of them (`text-adventure-agent`'s `TitleScreen`) reads nothing
|
|
56
|
+
* from the snapshot at all and subscribes to all of it for `session.start`.
|
|
57
|
+
*
|
|
58
|
+
* **Why publishing this does not reopen what `/internal` closed.**
|
|
59
|
+
* `useSessionCore` hands back the STORE — `subscribe`, `getSnapshot`,
|
|
60
|
+
* `connect`, `Symbol.dispose` — which is the framework's own plumbing, the same
|
|
61
|
+
* category as the providers and `buildAgentUrl` that live on
|
|
62
|
+
* `@alexkroman1/aai-ui/internal`. A client that holds it can subscribe out of
|
|
63
|
+
* band of React, dial a socket the mount did not, and dispose the session under
|
|
64
|
+
* the tree that is rendering it. What comes back from here is the SAME
|
|
65
|
+
* methods `useSession()` already publishes on its result, built into a fresh
|
|
66
|
+
* object rather than passed through, so the store is not reachable from it.
|
|
67
|
+
* There is no new capability here — only the existing one without the
|
|
68
|
+
* subscription tax.
|
|
69
|
+
*
|
|
70
|
+
* Identity-stable per core, so it is safe in a dependency array and in a
|
|
71
|
+
* `memo()` child's props: the methods are closures created once by
|
|
72
|
+
* `createBrowserSession`, and the object wrapping them is memoized on the core.
|
|
73
|
+
*
|
|
74
|
+
* Throws outside the provider `mountClient()` installs, like every session hook.
|
|
75
|
+
*
|
|
76
|
+
* @example A footer that acts on the session without re-rendering with it
|
|
77
|
+
* ```tsx
|
|
78
|
+
* import { useSessionActions, useSessionSelector } from "@alexkroman1/aai-ui";
|
|
79
|
+
*
|
|
80
|
+
* function Footer() {
|
|
81
|
+
* // Two narrow subscriptions and no snapshot read: this row re-renders when
|
|
82
|
+
* // `running` flips, and not on every transcript delta.
|
|
83
|
+
* const running = useSessionSelector((s) => s.running);
|
|
84
|
+
* const { toggle, end } = useSessionActions();
|
|
85
|
+
* return (
|
|
86
|
+
* <>
|
|
87
|
+
* <button onClick={toggle}>{running ? "Pause" : "Resume"}</button>
|
|
88
|
+
* <button onClick={end}>Hang up</button>
|
|
89
|
+
* </>
|
|
90
|
+
* );
|
|
91
|
+
* }
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* @returns The control methods — see {@link SessionActions}.
|
|
95
|
+
*
|
|
96
|
+
* @public
|
|
97
|
+
*/
|
|
98
|
+
function useSessionActions() {
|
|
99
|
+
const core = useSessionCore();
|
|
100
|
+
return useMemo(() => ({
|
|
101
|
+
start: core.start,
|
|
102
|
+
cancel: core.cancel,
|
|
103
|
+
sendText: core.sendText,
|
|
104
|
+
setMicMuted: core.setMicMuted,
|
|
105
|
+
resetState: core.resetState,
|
|
106
|
+
reset: core.reset,
|
|
107
|
+
restart: core.restart,
|
|
108
|
+
resume: core.resume,
|
|
109
|
+
disconnect: core.disconnect,
|
|
110
|
+
toggle: core.toggle,
|
|
111
|
+
end: core.end
|
|
112
|
+
}), [core]);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Return the live {@link Session}: the current snapshot fields plus the
|
|
116
|
+
* control methods (`start`, `toggle`, `reset`, `resetState`, `disconnect`,
|
|
117
|
+
* `cancel`, `end`).
|
|
118
|
+
*
|
|
119
|
+
* Throws if used outside the provider `mountClient()` installs (the error names
|
|
120
|
+
* `<SessionProvider>` — you only mount that yourself when bypassing
|
|
121
|
+
* `mountClient()`). Re-renders the component on *every* snapshot change; for a
|
|
122
|
+
* component that reads one field, prefer {@link useSessionSelector} for a
|
|
123
|
+
* targeted subscription.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```tsx
|
|
127
|
+
* import { useSession } from "@alexkroman1/aai-ui";
|
|
128
|
+
*
|
|
129
|
+
* function Controls() {
|
|
130
|
+
* const session = useSession();
|
|
131
|
+
* if (!session.started) return <button onClick={session.start}>Start</button>;
|
|
132
|
+
* return <button onClick={session.toggle}>{session.running ? "Pause" : "Resume"}</button>;
|
|
133
|
+
* }
|
|
134
|
+
* ```
|
|
135
|
+
*
|
|
136
|
+
* @public
|
|
137
|
+
*/
|
|
138
|
+
function useSession() {
|
|
139
|
+
const core = useSessionCore();
|
|
140
|
+
const snapshot = useSyncExternalStore(core.subscribe, core.getSnapshot);
|
|
141
|
+
const actions = useSessionActions();
|
|
142
|
+
return useMemo(() => ({
|
|
143
|
+
...snapshot,
|
|
144
|
+
...actions
|
|
145
|
+
}), [snapshot, actions]);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Subscribe to a narrow slice of the session snapshot.
|
|
149
|
+
*
|
|
150
|
+
* Unlike {@link useSession} — which re-renders the component on *every*
|
|
151
|
+
* snapshot change — this only triggers a re-render when the selected value
|
|
152
|
+
* changes (per `isEqual`, default `Object.is`). Use it for components that
|
|
153
|
+
* read a single field, e.g. `useSessionSelector((s) => s.running)`.
|
|
154
|
+
*
|
|
155
|
+
* The selector must be pure. It may run on every snapshot change, so keep it
|
|
156
|
+
* cheap; when it returns a derived object, pass a custom `isEqual` to avoid
|
|
157
|
+
* re-renders on referentially-new-but-equal results.
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```tsx
|
|
161
|
+
* import { useSessionSelector } from "@alexkroman1/aai-ui";
|
|
162
|
+
*
|
|
163
|
+
* // Re-renders when `running` flips, and on nothing else — not on every
|
|
164
|
+
* // transcript delta the way `useSession()` would.
|
|
165
|
+
* function MicDot() {
|
|
166
|
+
* const running = useSessionSelector((snapshot) => snapshot.running);
|
|
167
|
+
* return <span>{running ? "●" : "○"}</span>;
|
|
168
|
+
* }
|
|
169
|
+
* ```
|
|
170
|
+
*
|
|
171
|
+
* @param selector - Reads the slice out of the snapshot. Must be pure.
|
|
172
|
+
* @param isEqual - Compares two selected values. Defaults to `Object.is`.
|
|
173
|
+
* @returns The selected slice.
|
|
174
|
+
*
|
|
175
|
+
* @public
|
|
176
|
+
*/
|
|
177
|
+
function useSessionSelector(selector, isEqual = Object.is) {
|
|
178
|
+
const core = useSessionCore();
|
|
179
|
+
return useSyncExternalStoreWithSelector(core.subscribe, core.getSnapshot, core.getSnapshot, selector, isEqual);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* The two selectors below are MODULE-SCOPE functions, and that is load-bearing
|
|
183
|
+
* rather than tidy.
|
|
184
|
+
*
|
|
185
|
+
* `useSyncExternalStoreWithSelector` caches its selection keyed on the selector
|
|
186
|
+
* it was handed: a fresh arrow per render invalidates that memo every render,
|
|
187
|
+
* so the selector re-runs and the `isEqual` short-circuit protects only the
|
|
188
|
+
* re-RENDER, never the work. Hoisting them means the two fields more than one
|
|
189
|
+
* chrome ever reads are selected by one stable function for the life of the
|
|
190
|
+
* program — which is also the thing a caller writing the arrow inline cannot
|
|
191
|
+
* do for themselves, and the reason these two are hooks at all rather than a
|
|
192
|
+
* documented one-liner.
|
|
193
|
+
*/
|
|
194
|
+
const selectState = (s) => s.state;
|
|
195
|
+
const selectError = (s) => s.error;
|
|
196
|
+
/**
|
|
197
|
+
* The agent's live {@link AgentState} — `disconnected`, `connecting`, `ready`,
|
|
198
|
+
* `listening`, `thinking`, `speaking`, `error` — on its own narrow
|
|
199
|
+
* subscription.
|
|
200
|
+
*
|
|
201
|
+
* `useSessionSelector((s) => s.state)` spelled once. It is one of exactly two
|
|
202
|
+
* snapshot fields that more than one custom chrome ever selects (the other is
|
|
203
|
+
* {@link useSessionError}), and it had been written inline at eight sites —
|
|
204
|
+
* including inside this package and, worse, in `ConsoleShell`'s own `@example`,
|
|
205
|
+
* which taught the inline form to everyone who read it.
|
|
206
|
+
*
|
|
207
|
+
* **Named `useSessionStatus`, not `useSessionState`.** `useAgentState` is the
|
|
208
|
+
* SLOT hook — the agent's own synced application state, whatever a
|
|
209
|
+
* `sessionSlot()` projects — and `AgentState` here is the phase of the CALL.
|
|
210
|
+
* Two different concepts one letter apart, so the shorter-sounding name is the
|
|
211
|
+
* one deliberately not taken.
|
|
212
|
+
*
|
|
213
|
+
* Pair it with {@link AGENT_STATE_LABELS} for a rendered word; the raw member
|
|
214
|
+
* is a wire value, not a label.
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```tsx
|
|
218
|
+
* import { AGENT_STATE_LABELS, useSessionStatus } from "@alexkroman1/aai-ui";
|
|
219
|
+
*
|
|
220
|
+
* function StatusDot() {
|
|
221
|
+
* const status = useSessionStatus();
|
|
222
|
+
* return <span data-state={status}>{AGENT_STATE_LABELS[status]}</span>;
|
|
223
|
+
* }
|
|
224
|
+
* ```
|
|
225
|
+
*
|
|
226
|
+
* @returns The current agent state.
|
|
227
|
+
*
|
|
228
|
+
* @public
|
|
229
|
+
*/
|
|
230
|
+
function useSessionStatus() {
|
|
231
|
+
return useSessionSelector(selectState);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* The session's current {@link SessionError}, or `null` when there is none, on
|
|
235
|
+
* its own narrow subscription.
|
|
236
|
+
*
|
|
237
|
+
* The other half of {@link useSessionStatus} — the second of the two fields a
|
|
238
|
+
* custom chrome reads over and over, and the one whose absence is invisible:
|
|
239
|
+
* per the `fatalError` latch in `session/browser-session.ts` the error is the ONLY
|
|
240
|
+
* remaining signal that a session died, since the state beside it goes back to
|
|
241
|
+
* reading like a live one.
|
|
242
|
+
*
|
|
243
|
+
* A chrome rendering it owes `role="alert"` — which is what
|
|
244
|
+
* {@link SessionErrorBanner} is for, and why reaching for that beats reaching
|
|
245
|
+
* for this.
|
|
246
|
+
*
|
|
247
|
+
* @returns The current error, or `null`.
|
|
248
|
+
*
|
|
249
|
+
* @public
|
|
250
|
+
*/
|
|
251
|
+
function useSessionError() {
|
|
252
|
+
return useSessionSelector(selectError);
|
|
253
|
+
}
|
|
254
|
+
const ThemeCtx = createContext(DEFAULT_THEME);
|
|
255
|
+
/**
|
|
256
|
+
* Provides the theme the components read via `useTheme`. `mountClient()` installs
|
|
257
|
+
* it automatically (from `ClientConfig.theme`); a custom tree only needs it
|
|
258
|
+
* when bypassing `mountClient()` and mounting React itself.
|
|
259
|
+
*
|
|
260
|
+
* @internal
|
|
261
|
+
*/
|
|
262
|
+
function ThemeProvider({ value, children }) {
|
|
263
|
+
const merged = useMemo(() => value ? {
|
|
264
|
+
...DEFAULT_THEME,
|
|
265
|
+
...value
|
|
266
|
+
} : DEFAULT_THEME, [value]);
|
|
267
|
+
useThemeStyles(merged);
|
|
268
|
+
return createElement(ThemeCtx.Provider, { value: merged }, children);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* The custom property each {@link ClientTheme} field is published as.
|
|
272
|
+
*
|
|
273
|
+
* Named after the field, so `theme.surface` and `--aai-surface` are obviously
|
|
274
|
+
* the same thing, and prefixed for the reason `--aai-btn-bg` and
|
|
275
|
+
* `--aai-sidebar-w` already are: these land on `:root`, where an app's own
|
|
276
|
+
* variables live too.
|
|
277
|
+
*
|
|
278
|
+
* @internal
|
|
279
|
+
*/
|
|
280
|
+
const THEME_VARS = {
|
|
281
|
+
bg: "--aai-bg",
|
|
282
|
+
surface: "--aai-surface",
|
|
283
|
+
text: "--aai-text",
|
|
284
|
+
border: "--aai-border",
|
|
285
|
+
primary: "--aai-primary"
|
|
286
|
+
};
|
|
287
|
+
/**
|
|
288
|
+
* Publish the theme to CSS, and paint the page with it.
|
|
289
|
+
*
|
|
290
|
+
* Two jobs on one element deliberately — both are about the parts of the page
|
|
291
|
+
* React does not own.
|
|
292
|
+
*
|
|
293
|
+
* **The variables.** {@link useTheme} hands a component a JavaScript object, so
|
|
294
|
+
* everything it styles carries an inline `style={{ }}`; measured across the
|
|
295
|
+
* template tree the ratio of `theme.` reads to inline style objects is
|
|
296
|
+
* essentially one to one. A Tailwind class cannot see a JavaScript object — so
|
|
297
|
+
* the five values are written to `:root` as custom properties, `styles.css`
|
|
298
|
+
* maps them into the `@theme` block's `--color-*` namespace, and
|
|
299
|
+
* `className="bg-aai-surface text-aai-text border-aai-border"` works. This is
|
|
300
|
+
* ADDITIVE: `useTheme()` stays, because the derived tints in
|
|
301
|
+
* `components/_colors.ts` `color-mix` on the resolved values and a page is
|
|
302
|
+
* entitled to read them for a `satisfies`-pinned palette.
|
|
303
|
+
*
|
|
304
|
+
* **The background.** Components paint `theme.bg` on their own containers, but
|
|
305
|
+
* the page behind them keeps whatever the static `<style>` in index.html set.
|
|
306
|
+
* Any viewport wider (or taller) than the app column then shows that color as a
|
|
307
|
+
* border around the UI — which is how a cream theme ended up letterboxed in
|
|
308
|
+
* black. It stays an explicit `background` on `html` AND `body` rather than
|
|
309
|
+
* riding on the new variable: a variable only paints where some rule consumes
|
|
310
|
+
* it, and the two elements this is about are the two nothing in this package
|
|
311
|
+
* renders.
|
|
312
|
+
*/
|
|
313
|
+
function useThemeStyles(theme) {
|
|
314
|
+
const { bg, surface, text, border, primary } = theme;
|
|
315
|
+
useEffect(() => {
|
|
316
|
+
if (typeof document === "undefined") return;
|
|
317
|
+
const { body, documentElement: html } = document;
|
|
318
|
+
const values = {
|
|
319
|
+
bg,
|
|
320
|
+
surface,
|
|
321
|
+
text,
|
|
322
|
+
border,
|
|
323
|
+
primary
|
|
324
|
+
};
|
|
325
|
+
const previousVars = Object.entries(THEME_VARS).map(([field, prop]) => [
|
|
326
|
+
prop,
|
|
327
|
+
html.style.getPropertyValue(prop),
|
|
328
|
+
values[field]
|
|
329
|
+
]);
|
|
330
|
+
for (const [prop, , next] of previousVars) html.style.setProperty(prop, next);
|
|
331
|
+
const previousBg = {
|
|
332
|
+
body: body.style.background,
|
|
333
|
+
html: html.style.background
|
|
334
|
+
};
|
|
335
|
+
body.style.background = bg;
|
|
336
|
+
html.style.background = bg;
|
|
337
|
+
return () => {
|
|
338
|
+
for (const [prop, before] of previousVars) if (before === "") html.style.removeProperty(prop);
|
|
339
|
+
else html.style.setProperty(prop, before);
|
|
340
|
+
body.style.background = previousBg.body;
|
|
341
|
+
html.style.background = previousBg.html;
|
|
342
|
+
};
|
|
343
|
+
}, [
|
|
344
|
+
bg,
|
|
345
|
+
surface,
|
|
346
|
+
text,
|
|
347
|
+
border,
|
|
348
|
+
primary
|
|
349
|
+
]);
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Read the resolved theme (every {@link ClientTheme} field filled with its
|
|
353
|
+
* default) from the nearest theme context. Returns the default theme when no
|
|
354
|
+
* provider is present, so components can call it unconditionally.
|
|
355
|
+
*
|
|
356
|
+
* This is how a custom component stays on the agent's palette: a
|
|
357
|
+
* `mountClient({ theme })` override reaches it here, where a hardcoded colour or a
|
|
358
|
+
* Tailwind class cannot see it.
|
|
359
|
+
*
|
|
360
|
+
* @example
|
|
361
|
+
* ```tsx
|
|
362
|
+
* import { useTheme } from "@alexkroman1/aai-ui";
|
|
363
|
+
*
|
|
364
|
+
* function Total({ amount }: { amount: string }) {
|
|
365
|
+
* const theme = useTheme();
|
|
366
|
+
* return (
|
|
367
|
+
* <strong style={{ color: theme.primary, background: theme.surface }}>
|
|
368
|
+
* {amount}
|
|
369
|
+
* </strong>
|
|
370
|
+
* );
|
|
371
|
+
* }
|
|
372
|
+
* ```
|
|
373
|
+
*
|
|
374
|
+
* @returns Every {@link ClientTheme} field, filled in.
|
|
375
|
+
*
|
|
376
|
+
* @public
|
|
377
|
+
*/
|
|
378
|
+
function useTheme() {
|
|
379
|
+
return useContext(ThemeCtx);
|
|
380
|
+
}
|
|
381
|
+
//#endregion
|
|
382
|
+
export { SessionProvider, ThemeProvider, useOptionalSessionCore, useSession, useSessionActions, useSessionCore, useSessionError, useSessionSelector, useSessionStatus, useTheme };
|