@aparte/react 0.24.0 → 0.25.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 +4 -0
- package/dist/components/AparteChat.d.ts.map +1 -1
- package/dist/components/AparteToolUI.d.ts +47 -0
- package/dist/components/AparteToolUI.d.ts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +82 -9
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -4,6 +4,10 @@ The **React AI chat component** of [aparté](https://github.com/apartejs/aparte)
|
|
|
4
4
|
`<AparteChat>` (React 18/19) plus hooks (`useAparteChat`, `useAparteClient`, `useConversationManager`) over the
|
|
5
5
|
framework-agnostic web components in `@aparte/core`.
|
|
6
6
|
|
|
7
|
+
Tested in CI with React 19, and in a Next 15 App Router page rendered on the server and hydrated
|
|
8
|
+
with no mismatch. `<AparteToolUI name="…">` draws a tool call with your own component, with your
|
|
9
|
+
app's context.
|
|
10
|
+
|
|
7
11
|
```bash
|
|
8
12
|
npm install @aparte/react @aparte/core react react-dom
|
|
9
13
|
```
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AparteChat.d.ts","sourceRoot":"","sources":["../../src/components/AparteChat.tsx"],"names":[],"mappings":"AAEA,OAAO,KASN,MAAM,OAAO,CAAC;AAEf,OAAO,
|
|
1
|
+
{"version":3,"file":"AparteChat.d.ts","sourceRoot":"","sources":["../../src/components/AparteChat.tsx"],"names":[],"mappings":"AAEA,OAAO,KASN,MAAM,OAAO,CAAC;AAEf,OAAO,EAA6G,KAAK,YAAY,EAAE,KAAK,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAC1L,OAAO,KAAK,EAAE,aAAa,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAEjG,MAAM,WAAW,eAAe;IAC5B;;;;;;;;;;OAUG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;IAEZ;;;;OAIG;IACH,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,+EAA+E;IAC/E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,8FAA8F;IAC9F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,KAAK,CAAC,SAAS,CAAC;IAC3D;;;;OAIG;IACH,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B;;;;OAIG;IACH,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAChC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAE1B;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAC;IACvD;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,uBAAuB,KAAK,IAAI,CAAC;IACrD;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,aAAa,EAAE,KAAK,IAAI,CAAC;IACvD,0EAA0E;IAC1E,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IACrD,gEAAgE;IAChE,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7C,6EAA6E;IAC7E,qBAAqB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAE7C;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;CACzB;AAgBD,eAAO,MAAM,UAAU,iGA+QrB,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Your own React component in a tool call, or in a segment of a type of your own.
|
|
3
|
+
*
|
|
4
|
+
* `<AparteToolUI name="get_weather">{(tool) => <WeatherCard … />}</AparteToolUI>` draws every
|
|
5
|
+
* `get_weather` call with your component for as long as the declaration is mounted, and gives
|
|
6
|
+
* the calls back to core's row when it unmounts. The card is a portal: it renders from where
|
|
7
|
+
* the declaration sits in your tree, so it reads your contexts (a theme, a router, a store) like
|
|
8
|
+
* any other component there.
|
|
9
|
+
*
|
|
10
|
+
* Underneath it is core's `overrideToolRenderer` + `defineMountedToolRenderer`: core hands each
|
|
11
|
+
* matching call an element and every newer state of the call; this component keeps the elements
|
|
12
|
+
* in a small store React reads, and portals the children into each one. Nothing registers on
|
|
13
|
+
* the server, so a server render draws nothing here, and the calls are drawn in the browser.
|
|
14
|
+
*/
|
|
15
|
+
import React from 'react';
|
|
16
|
+
import { type AparteConfig, type AparteSegmentBase, type AparteToolCallView, type AparteToolMatcher } from '@aparte/core';
|
|
17
|
+
export interface AparteToolUIProps<I = unknown, R = unknown> {
|
|
18
|
+
/** The tool this draws: a name, a `RegExp`, or `'*'` for every tool. */
|
|
19
|
+
name: AparteToolMatcher;
|
|
20
|
+
/** The config to declare it on. Defaults to the global one. */
|
|
21
|
+
config?: AparteConfig;
|
|
22
|
+
/** What to draw for one call: its id, name, input, status, result and the rest. */
|
|
23
|
+
children: (tool: AparteToolCallView<I, R>) => React.ReactNode;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Draw a tool call with your own component, for as long as this is mounted.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* <AparteToolUI<{ city: string }> name="get_weather">
|
|
30
|
+
* {(tool) => <WeatherCard city={tool.input.city} status={tool.status} />}
|
|
31
|
+
* </AparteToolUI>
|
|
32
|
+
*/
|
|
33
|
+
export declare function AparteToolUI<I = unknown, R = unknown>({ name, config, children }: AparteToolUIProps<I, R>): React.JSX.Element;
|
|
34
|
+
export interface AparteSegmentUIProps<T extends AparteSegmentBase> {
|
|
35
|
+
/** The segment `type` this draws — one your stream emits, or a built-in such as `code`. */
|
|
36
|
+
type: T['type'];
|
|
37
|
+
/** The config to declare it on. Defaults to the global one. */
|
|
38
|
+
config?: AparteConfig;
|
|
39
|
+
/** What to draw for one segment, handed its latest state. */
|
|
40
|
+
children: (segment: T) => React.ReactNode;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Draw a segment type with your own component, for as long as this is mounted. Taking the
|
|
44
|
+
* last declaration off a built-in type gives it back to core's renderer.
|
|
45
|
+
*/
|
|
46
|
+
export declare function AparteSegmentUI<T extends AparteSegmentBase>({ type, config, children }: AparteSegmentUIProps<T>): React.JSX.Element;
|
|
47
|
+
//# sourceMappingURL=AparteToolUI.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AparteToolUI.d.ts","sourceRoot":"","sources":["../../src/components/AparteToolUI.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAoD,MAAM,OAAO,CAAC;AAEzE,OAAO,EAKH,KAAK,YAAY,EAEjB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACzB,MAAM,cAAc,CAAC;AAmDtB,MAAM,WAAW,iBAAiB,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO;IACvD,wEAAwE;IACxE,IAAI,EAAE,iBAAiB,CAAC;IACxB,+DAA+D;IAC/D,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,mFAAmF;IACnF,QAAQ,EAAE,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,SAAS,CAAC;CACjE;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,qBAUzG;AAED,MAAM,WAAW,oBAAoB,CAAC,CAAC,SAAS,iBAAiB;IAC7D,2FAA2F;IAC3F,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IAChB,+DAA+D;IAC/D,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,6DAA6D;IAC7D,QAAQ,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,KAAK,CAAC,SAAS,CAAC;CAC7C;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,iBAAiB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,oBAAoB,CAAC,CAAC,CAAC,qBAQ/G"}
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,9 @@ export { useConversationManager } from './hooks/useConversationManager.js';
|
|
|
13
13
|
export type { UseConversationManager } from './hooks/useConversationManager.js';
|
|
14
14
|
export { AparteUi } from './components/AparteUi.js';
|
|
15
15
|
export type { AparteUiProps } from './components/AparteUi.js';
|
|
16
|
+
export { AparteToolUI, AparteSegmentUI } from './components/AparteToolUI.js';
|
|
17
|
+
export type { AparteToolUIProps, AparteSegmentUIProps } from './components/AparteToolUI.js';
|
|
18
|
+
export type { AparteToolCallView } from '@aparte/core';
|
|
16
19
|
export type { AparteUiHandle } from '@aparte/core';
|
|
17
20
|
export type { AparteMessage, AparteSendEventDetail, AparteActionEventDetail, AparteSegment, AparteTextSegment, AparteCodeSegment, AparteThinkingSegment, } from './types.js';
|
|
18
21
|
import type { AparteElementAttributes, AparteElementTagName, AparteTemplateAttrs } from '@aparte/core';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,YAAY,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAKlE,YAAY,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAG5D,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAG9D,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,YAAY,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,YAAY,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAChF,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,YAAY,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAKlE,YAAY,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAG5D,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAG9D,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,YAAY,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,YAAY,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAChF,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC7E,YAAY,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAC5F,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAGvD,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,YAAY,EACR,aAAa,EACb,qBAAqB,EACrB,uBAAuB,EACvB,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,GACxB,MAAM,YAAY,CAAC;AAGpB,OAAO,KAAK,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAGvG,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAgBpC;;;;;;;;;;;GAWG;AACH;;;;;;;;;GASG;AACH,KAAK,cAAc,CAAC,CAAC,IACjB,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,KAAK,CAAC,cAAc,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC,GACpF,mBAAmB,CAAC,CAAC,CAAC,CAAC;AAE7B;;;;;;;;;;GAUG;AACH,KAAK,uBAAuB,GAAG;KAC1B,CAAC,IAAI,oBAAoB,GAAG,cAAc,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC;CAC1E,CAAC;AAEF,OAAO,CAAC,MAAM,CAAC;IAEX,UAAU,GAAG,CAAC;QACV,UAAU,iBAAkB,SAAQ,uBAAuB;SAAG;KACjE;CACJ;AAED,OAAO,QAAQ,OAAO,CAAC;IAEnB,UAAU,GAAG,CAAC;QACV,UAAU,iBAAkB,SAAQ,uBAAuB;SAAG;KACjE;CACJ"}
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsxs, jsx } from "react/jsx-runtime";
|
|
3
|
-
import React, { forwardRef, useId, useRef,
|
|
4
|
-
import { aparteGlobalConfig, AparteChatHost, isAwaitingReply, AparteClient, AparteConversationManager, APARTE_DEFAULT_UI_EVENTS, applyElementProps } from "@aparte/core";
|
|
2
|
+
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
3
|
+
import React, { forwardRef, useId, useRef, useSyncExternalStore, useState, useCallback, useEffect, useImperativeHandle, useMemo } from "react";
|
|
4
|
+
import { aparteGlobalConfig, releaseDeferredHydration, AparteChatHost, isAwaitingReply, AparteClient, AparteConversationManager, APARTE_DEFAULT_UI_EVENTS, applyElementProps, defineMountedSegmentRenderer, overrideSegmentRenderer, defineMountedToolRenderer } from "@aparte/core";
|
|
5
|
+
import { createPortal } from "react-dom";
|
|
5
6
|
function afterReactRender(cb) {
|
|
6
7
|
if (typeof document !== "undefined" && document.visibilityState === "hidden") setTimeout(cb, 0);
|
|
7
8
|
else requestAnimationFrame(() => cb());
|
|
8
9
|
}
|
|
9
10
|
const NO_MESSAGES = [];
|
|
11
|
+
const subscribeToNothing = () => () => {
|
|
12
|
+
};
|
|
13
|
+
const isHydrating = () => true;
|
|
14
|
+
const isNotHydrating = () => false;
|
|
10
15
|
const AparteChat = forwardRef(function AparteChat2({
|
|
11
16
|
messages = NO_MESSAGES,
|
|
12
17
|
placeholder = "Type a message...",
|
|
@@ -40,6 +45,7 @@ const AparteChat = forwardRef(function AparteChat2({
|
|
|
40
45
|
const generatedId = `aparte-chat-${useId().replace(/:/g, "")}`;
|
|
41
46
|
const hostId = providedId ?? generatedId;
|
|
42
47
|
const hostElRef = useRef(null);
|
|
48
|
+
const hydrating = useSyncExternalStore(subscribeToNothing, isNotHydrating, isHydrating);
|
|
43
49
|
const viewportRef = useRef(null);
|
|
44
50
|
const composerRef = useRef(null);
|
|
45
51
|
const messagesRef = useRef(messages);
|
|
@@ -57,7 +63,6 @@ const AparteChat = forwardRef(function AparteChat2({
|
|
|
57
63
|
const lastConvRef = useRef(conversationId);
|
|
58
64
|
const cbRef = useRef({ onMessageSent, onAction, onMessagesChange, onMessageAppended, onTypingChange, onConversationCreated });
|
|
59
65
|
cbRef.current = { onMessageSent, onAction, onMessagesChange, onMessageAppended, onTypingChange, onConversationCreated };
|
|
60
|
-
const emittedRef = useRef(/* @__PURE__ */ new WeakSet());
|
|
61
66
|
const applyMessages = (m) => {
|
|
62
67
|
messagesRef.current = m;
|
|
63
68
|
setRenderMessages(m);
|
|
@@ -65,16 +70,14 @@ const AparteChat = forwardRef(function AparteChat2({
|
|
|
65
70
|
useEffect(() => {
|
|
66
71
|
const host = hostElRef.current;
|
|
67
72
|
if (!host) return;
|
|
73
|
+
releaseDeferredHydration(host);
|
|
68
74
|
const binding = {
|
|
69
75
|
hostId,
|
|
70
76
|
host,
|
|
71
77
|
viewport: viewportRef.current,
|
|
72
78
|
getMessages: () => messagesRef.current,
|
|
73
79
|
setMessages: (m) => applyMessages(m),
|
|
74
|
-
onMessagesChange: (m) =>
|
|
75
|
-
emittedRef.current.add(m);
|
|
76
|
-
cbRef.current.onMessagesChange?.(m);
|
|
77
|
-
},
|
|
80
|
+
onMessagesChange: (m) => cbRef.current.onMessagesChange?.(m),
|
|
78
81
|
onMessageAppended: (m) => cbRef.current.onMessageAppended?.(m),
|
|
79
82
|
onTypingChange: (t) => {
|
|
80
83
|
setTypingActive(t);
|
|
@@ -99,7 +102,8 @@ const AparteChat = forwardRef(function AparteChat2({
|
|
|
99
102
|
};
|
|
100
103
|
}, [hostId]);
|
|
101
104
|
useEffect(() => {
|
|
102
|
-
|
|
105
|
+
const echo = hostRef.current?.takeEcho(messages) ?? false;
|
|
106
|
+
if (echo || messages === messagesRef.current) return;
|
|
103
107
|
applyMessages(messages);
|
|
104
108
|
if (messages.length === 0) hostRef.current?.clearRenderCache();
|
|
105
109
|
}, [messages]);
|
|
@@ -169,6 +173,7 @@ const AparteChat = forwardRef(function AparteChat2({
|
|
|
169
173
|
className: `aparte-chat-container${centerWhenEmpty ? " aparte-chat-container--auto-center" : ""}${className ? ` ${className}` : ""}`,
|
|
170
174
|
style,
|
|
171
175
|
"data-aparte-chat": "",
|
|
176
|
+
...hydrating ? { "defer-hydration": "" } : {},
|
|
172
177
|
...overlayComposer ? { "overlay-composer": "" } : {},
|
|
173
178
|
"data-aparte-empty": centerWhenEmpty && renderMessages.length === 0 && !waiting ? "" : void 0,
|
|
174
179
|
id: hostId,
|
|
@@ -359,8 +364,76 @@ const AparteUi = forwardRef(function AparteUi2({ name, props = {}, onElementEven
|
|
|
359
364
|
return /* @__PURE__ */ jsx("span", { ref: hostRef, style: { display: "contents" } });
|
|
360
365
|
});
|
|
361
366
|
AparteUi.displayName = "AparteUi";
|
|
367
|
+
const NONE = /* @__PURE__ */ new Map();
|
|
368
|
+
const serverSnapshot = () => NONE;
|
|
369
|
+
class MountedViews {
|
|
370
|
+
views = /* @__PURE__ */ new Map();
|
|
371
|
+
snapshot = NONE;
|
|
372
|
+
listeners = /* @__PURE__ */ new Set();
|
|
373
|
+
queued = false;
|
|
374
|
+
// A key per element, not the call's id: a model can issue `call_0` on every turn.
|
|
375
|
+
keys = /* @__PURE__ */ new WeakMap();
|
|
376
|
+
serial = 0;
|
|
377
|
+
subscribe = (listener) => {
|
|
378
|
+
this.listeners.add(listener);
|
|
379
|
+
return () => {
|
|
380
|
+
this.listeners.delete(listener);
|
|
381
|
+
};
|
|
382
|
+
};
|
|
383
|
+
getSnapshot = () => this.snapshot;
|
|
384
|
+
keyOf = (host) => this.keys.get(host);
|
|
385
|
+
mount = (host, view) => {
|
|
386
|
+
this.keys.set(host, String(++this.serial));
|
|
387
|
+
this.views.set(host, view);
|
|
388
|
+
this.changed();
|
|
389
|
+
return {
|
|
390
|
+
update: (next) => {
|
|
391
|
+
this.views.set(host, next);
|
|
392
|
+
this.changed();
|
|
393
|
+
},
|
|
394
|
+
destroy: () => {
|
|
395
|
+
if (this.views.delete(host)) this.changed();
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
};
|
|
399
|
+
// Core mounts from its own rendering, which can run inside another component's commit
|
|
400
|
+
// (the chat's effects append the bubbles). React hears about it in a microtask, once for
|
|
401
|
+
// however many calls changed together.
|
|
402
|
+
changed() {
|
|
403
|
+
if (this.queued) return;
|
|
404
|
+
this.queued = true;
|
|
405
|
+
queueMicrotask(() => {
|
|
406
|
+
this.queued = false;
|
|
407
|
+
this.snapshot = new Map(this.views);
|
|
408
|
+
for (const listener of this.listeners) listener();
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
const matcherKey = (match) => typeof match === "string" ? match : String(match);
|
|
413
|
+
function AparteToolUI({ name, config, children }) {
|
|
414
|
+
const [views] = useState(() => new MountedViews());
|
|
415
|
+
const key = matcherKey(name);
|
|
416
|
+
useEffect(
|
|
417
|
+
() => (config ?? aparteGlobalConfig).overrideToolRenderer(name, defineMountedToolRenderer(views.mount)),
|
|
418
|
+
// `name` is read through its key: the same pattern written again is the same declaration.
|
|
419
|
+
[key, config, views]
|
|
420
|
+
);
|
|
421
|
+
const mounted = useSyncExternalStore(views.subscribe, views.getSnapshot, serverSnapshot);
|
|
422
|
+
return /* @__PURE__ */ jsx(Fragment, { children: Array.from(mounted, ([host, view]) => createPortal(children(view), host, views.keyOf(host))) });
|
|
423
|
+
}
|
|
424
|
+
function AparteSegmentUI({ type, config, children }) {
|
|
425
|
+
const [views] = useState(() => new MountedViews());
|
|
426
|
+
useEffect(() => {
|
|
427
|
+
const renderer = defineMountedSegmentRenderer(type, views.mount);
|
|
428
|
+
return config ? overrideSegmentRenderer(renderer, config) : overrideSegmentRenderer(renderer);
|
|
429
|
+
}, [type, config, views]);
|
|
430
|
+
const mounted = useSyncExternalStore(views.subscribe, views.getSnapshot, serverSnapshot);
|
|
431
|
+
return /* @__PURE__ */ jsx(Fragment, { children: Array.from(mounted, ([host, segment]) => createPortal(children(segment), host, views.keyOf(host))) });
|
|
432
|
+
}
|
|
362
433
|
export {
|
|
363
434
|
AparteChat,
|
|
435
|
+
AparteSegmentUI,
|
|
436
|
+
AparteToolUI,
|
|
364
437
|
AparteUi,
|
|
365
438
|
useAparteChat,
|
|
366
439
|
useAparteClient,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/internal/after-render.ts","../src/components/AparteChat.tsx","../src/hooks/useAparteChat.ts","../src/hooks/useAparteClient.ts","../src/hooks/useConversationManager.ts","../src/components/AparteUi.tsx"],"sourcesContent":["/**\n * Run `cb` once React has committed the render in flight: on the next animation frame — or,\n * in a hidden tab, on the next task, since a hidden document runs no animation frame while\n * React still commits there. Waiting on a frame there held a compaction's save until the\n * reader came back, and lost it if they closed the tab first.\n */\nexport function afterReactRender(cb: () => void): void {\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') setTimeout(cb, 0);\n else requestAnimationFrame(() => cb());\n}\n","'use client';\n\nimport React, {\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n useSyncExternalStore,\n forwardRef,\n useImperativeHandle,\n} from 'react';\nimport { afterReactRender } from '../internal/after-render.js';\nimport { AparteChatHost, aparteGlobalConfig, isAwaitingReply, type AparteChatHostBinding, type AparteConfig, type AparteChatImperativeApi } from '@aparte/core';\nimport type { AparteMessage, AparteSendEventDetail, AparteActionEventDetail } from '../types.js';\n\nexport interface AparteChatProps {\n /**\n * The host element's `id`, and therefore the `targetId` every event this chat\n * dispatches carries.\n *\n * Generated when omitted, which is the right default — but it used to be\n * generated and neither accepted nor exposed, so\n * `AparteClientOptions.scopeToTargetId` (the documented way to run several\n * independent clients on one page) was unreachable from this component: there\n * was no way to learn the id the client had to match. Angular exposed it; React,\n * Vue and Svelte did not.\n */\n id?: string;\n\n /**\n * Messages on the active path. **Optional** — omit for an uncontrolled chat\n * that starts empty (defaults to `[]`); pass it together with\n * `onMessagesChange` to control the list from the parent.\n */\n messages?: AparteMessage[];\n placeholder?: string;\n disabled?: boolean;\n isTyping?: boolean;\n typingText?: string;\n /** When false, Shift+Enter submits and a bare Enter inserts a newline. */\n submitOnEnter?: boolean;\n /** Freeze viewport spacer recalculation for this many ms after a conv swap. */\n layoutTransitionMs?: number;\n /**\n * Opt in to the \"centered composer when empty\" layout: while the message\n * list is empty the composer sits vertically centered with the `emptyState`\n * content above it, then slides to the bottom on the first message (~0.3s).\n * Off by default — purely additive: adds the `aparte-chat-container--auto-center`\n * modifier + a `data-aparte-empty` attribute that the shipped `aparte.css` recipe\n * keys off. No effect unless you also render an `emptyState`.\n */\n centerWhenEmpty?: boolean;\n /**\n * The conversation is on its way: the viewport draws two skeleton turns, the empty\n * state stays off and the composer is disabled until it lands. The conversation\n * controller sets this itself while it fetches through your adapter's `loadFull()`;\n * the prop is for a wait of your own. Off by default.\n */\n loading?: boolean;\n /**\n * Overlay the composer on the transcript (the ChatGPT anatomy): the scroll\n * surface spans the whole column, the scrollbar runs edge to edge, and the\n * composer floats over it. Core measures the floating stack and keeps content,\n * spacer and the scroll button clear of it. Read when the viewport mounts.\n */\n overlayComposer?: boolean;\n /**\n * Add the file picker + chips strip to the default composer shell. **Off by\n * default**, because the capability needs a host that consumes the files: an\n * `AparteClient` inlines them per its `rawFileInject` option, but if you drive\n * your own loop you must read `event.files` in `onMessageSent` — otherwise the\n * file the user deliberately attached is dropped in silence. No effect when you\n * pass your own `composer` (drop `<aparte-composer-add-attachment>` and\n * `<aparte-composer-attachments>` in it yourself).\n */\n attachments?: boolean;\n /**\n * Render the `<aparte-elicitation>` presenter inside the host — **on by default**,\n * as it is in `<aparte-chat>`: the built-in approval gate and `requestUserInput()`\n * ask through it, and it renders nothing until something asks. Pass `false` when\n * you mount a presenter of your own (`setElicitationPresenter`) or place the element\n * yourself. The first consumer to hit this appended the element by hand next to the\n * React tree, guided by a warning that named an `<aparte-chat>` this wrapper does not\n * render.\n */\n elicitation?: boolean;\n /** Extra class names for the root element (`[data-aparte-chat]`), merged after core's own. */\n className?: string;\n /** Inline style for the root element (`[data-aparte-chat]`). */\n style?: React.CSSProperties;\n /**\n * Active conversation id. When set, the wrapper loads/persists via the\n * `AparteConversationManager` registered in `aparteGlobalConfig` (set `null` to deselect).\n */\n conversationId?: string | null;\n /**\n * Custom composer content, rendered inside `<aparte-composer>` in place of the\n * default shell (input · send, plus the attachment primitives when\n * `attachments` is set). Compose the headless\n * `aparte-composer-*` primitives freely — e.g. a skin-specific layout. Omit for\n * the default shell. The `<aparte-composer>` element (and its placeholder /\n * disabled / submit-on-enter behaviour) is always provided by the wrapper.\n */\n composer?: React.ReactNode;\n /**\n * Render your OWN element per message in place of `<aparte-chat-bubble>`.\n * Opt-in — omit for the default bubble. The returned node is driven by the\n * reactive message list, so it updates live during streaming (no need to\n * implement any imperative interface): re-render from `message.content` /\n * `message.segments`. Note the built-in action bar (retry/edit/branch) and\n * the imperative streaming push are the native bubble's — a custom bubble\n * owns whatever it wires (it can dispatch `aparte-retry` etc. or call the\n * wrapper's imperative API).\n */\n renderBubble?: (message: AparteMessage) => React.ReactNode;\n /**\n * Welcome / placeholder content shown INSIDE the viewport while there are no\n * messages (a real \"empty state\" region, not a workaround via `aboveComposer`).\n * Replaced by the message list on the first message.\n */\n emptyState?: React.ReactNode;\n /**\n * Content rendered ABOVE the composer (e.g. a disclaimer banner, a\n * \"scroll to bottom\" affordance, a context chip). Ignored when a full\n * custom `composer` replaces the shell.\n */\n aboveComposer?: React.ReactNode;\n /**\n * The composer's bottom row — a mode picker, a model selector, a token counter.\n * Rendered inside core's `<aparte-composer-toolbar>`; nothing is rendered at all\n * when you pass nothing, so an unused row never draws its separator.\n *\n * **Placement is the DOM order.** There is no left/center/right slot: put things in\n * the order you want them, and push a control (with everything after it) to the end\n * with `margin-inline-start: auto`. That is a logical property, so the row reads\n * correctly in a right-to-left locale with no extra work.\n *\n * Ignored when a full custom `composer` replaces the shell.\n *\n * @example\n * <AparteChat\n * messages={messages}\n * onMessagesChange={setMessages}\n * toolbar={<>\n * <ModePicker value={mode} onChange={setMode} />\n * <ModelSelector style={{ marginInlineStart: 'auto' }} />\n * </>}\n * />\n */\n toolbar?: React.ReactNode;\n\n /**\n * Notification that the user submitted a message from the composer. The\n * user's message is **appended to the thread automatically** (optimistic UI)\n * before this fires — do NOT add it again here. In an uncontrolled chat,\n * appending it duplicates it; in a controlled chat, mirror it into your own\n * `messages`. Use this for side-effects: scroll, analytics, backend send.\n */\n onMessageSent?: (event: AparteSendEventDetail) => void;\n /**\n * Fired when a custom bubble action (registered via\n * `aparteGlobalConfig.registerAction` with `zones: ['bubble']`) is clicked — a typed\n * wrapper over the bubbling `aparte-action` DOM event. Dispatch on `detail.actionId`.\n */\n onAction?: (detail: AparteActionEventDetail) => void;\n /**\n * Fired when the active message path changes (branch navigation / edit /\n * retry / streaming). Set the result back as the `messages` prop.\n */\n onMessagesChange?: (messages: AparteMessage[]) => void;\n /** Fired when a message is appended internally (e.g. by AparteClient). */\n onMessageAppended?: (message: AparteMessage) => void;\n /** Fired when the typing/\"thinking\" indicator should toggle. */\n onTypingChange?: (isTyping: boolean) => void;\n /** Fired when the controller lazily creates a conversation on first send. */\n onConversationCreated?: (id: string) => void;\n\n /**\n * Instance {@link AparteConfig} for this chat. When set, every aparté\n * component rendered inside resolves THIS config instead of the global\n * `aparteGlobalConfig` — letting several independently-configured chats\n * (different providers, tools, renderers) coexist on one page. Omit for the\n * global config. Read once when the host mounts.\n */\n config?: AparteConfig;\n}\n\n/**\n * The omitted-prop default, hoisted out of the render: a destructuring default\n * `messages = []` is a NEW array on every render, and the parent-push effect below\n * compares by identity — so an uncontrolled `<AparteChat>` applied an empty list on\n * every render, wiping its own thread, and looped. One module-level array is safe to\n * share across instances: it is never mutated (the host replaces the list).\n */\nconst NO_MESSAGES: AparteMessage[] = [];\n\nexport const AparteChat = forwardRef<AparteChatImperativeApi, AparteChatProps>(function AparteChat(\n {\n messages = NO_MESSAGES,\n placeholder = 'Type a message...',\n disabled = false,\n isTyping = false,\n typingText,\n submitOnEnter = true,\n layoutTransitionMs = 0,\n centerWhenEmpty = false,\n loading = false,\n overlayComposer = false,\n attachments = false,\n elicitation = true,\n className,\n style,\n conversationId = null,\n composer,\n renderBubble,\n emptyState,\n aboveComposer,\n toolbar,\n config,\n onMessageSent,\n onAction,\n onMessagesChange,\n onMessageAppended,\n onTypingChange,\n onConversationCreated,\n id: providedId,\n },\n ref,\n) {\n // useId() is SSR-stable (server and client agree), so no hydration mismatch.\n // Strip ':' so the id is also safe in CSS/querySelector, not just getElementById.\n // A caller-supplied id wins, so `scopeToTargetId` has something to match.\n const generatedId = `aparte-chat-${useId().replace(/:/g, '')}`;\n const hostId = providedId ?? generatedId;\n const hostElRef = useRef<HTMLDivElement>(null);\n const viewportRef = useRef<HTMLElement>(null);\n const composerRef = useRef<HTMLElement>(null);\n\n // Authoritative message list lives in a ref (the host reads it synchronously);\n // `renderMessages` state drives the declarative bubble list.\n const messagesRef = useRef<AparteMessage[]>(messages);\n const [renderMessages, setRenderMessages] = useState<AparteMessage[]>(messages);\n const [typingActive, setTypingActive] = useState(isTyping);\n // The controller's wait (a conversation fetched through the adapter) OR the consumer's.\n const [hostLoading, setHostLoading] = useState(false);\n const waiting = loading || hostLoading;\n // The two locale strings this component draws itself, read again on a language switch: a\n // config change re-renders nothing else here. One snapshot, so only a changed string re-renders.\n const labelConfig = config ?? aparteGlobalConfig;\n const readLabels = (): string => `${labelConfig.t('loadingConversation')}\\u0000${labelConfig.t('typing')}`;\n const labels = useSyncExternalStore(useCallback((notify) => labelConfig.subscribe(notify), [labelConfig]), readLabels, readLabels);\n const [loadingText = '', localeTyping = ''] = labels.split('\\u0000');\n // The status line's word, from the locale like the rest of the chat, unless the prop says.\n const typingLabel = typingText ?? localeTyping;\n const [, setIsStreaming] = useState(false);\n\n const hostRef = useRef<AparteChatHost | null>(null);\n const lastConvRef = useRef<string | null>(conversationId);\n\n // Keep the latest prop callbacks in a ref so the host's stable binding always\n // calls the current handlers without recreating the host.\n const cbRef = useRef({ onMessageSent, onAction, onMessagesChange, onMessageAppended, onTypingChange, onConversationCreated });\n cbRef.current = { onMessageSent, onAction, onMessagesChange, onMessageAppended, onTypingChange, onConversationCreated };\n\n // Every list the host emitted. In controlled mode the prop comes back as one of them\n // — and, when the parent's render lands after the host's next write (WebKit commits\n // React after the host's frame), as an EARLIER one. An echo is not the parent's word:\n // applying it rolled the list back, the host built its next writes on it, and a\n // finished reply lost its text and its tool rows.\n const emittedRef = useRef(new WeakSet<AparteMessage[]>());\n\n const applyMessages = (m: AparteMessage[]) => {\n messagesRef.current = m;\n setRenderMessages(m);\n };\n\n // Create the host once after mount.\n useEffect(() => {\n const host = hostElRef.current;\n if (!host) return;\n const binding: AparteChatHostBinding = {\n hostId,\n host,\n viewport: viewportRef.current,\n getMessages: () => messagesRef.current,\n setMessages: (m) => applyMessages(m as AparteMessage[]),\n onMessagesChange: (m) => {\n emittedRef.current.add(m as AparteMessage[]);\n cbRef.current.onMessagesChange?.(m as AparteMessage[]);\n },\n onMessageAppended: (m) => cbRef.current.onMessageAppended?.(m as AparteMessage),\n onTypingChange: (t) => { setTypingActive(t); cbRef.current.onTypingChange?.(t); },\n onStreamingChange: (id) => setIsStreaming(id !== null),\n afterRender: afterReactRender,\n resetComposer: () => (composerRef.current as unknown as { reset?: () => void })?.reset?.(),\n onLoadingChange: (on) => setHostLoading(on),\n };\n const h = new AparteChatHost(binding, {\n layoutTransitionMs,\n conversationId: conversationId ?? null,\n onConversationCreated: (id) => cbRef.current.onConversationCreated?.(id),\n config,\n });\n hostRef.current = h;\n const teardown = h.bind();\n return () => { teardown(); hostRef.current = null; };\n // The host is created once per mount (keyed by the stable hostId); prop\n // changes flow through cbRef / dedicated effects, not by recreating it.\n }, [hostId]);\n\n // Parent push: sync the prop into the authoritative list — unless it is the host's\n // own emit coming back, current or stale (see `emittedRef`).\n useEffect(() => {\n if (messages === messagesRef.current || emittedRef.current.has(messages)) return;\n applyMessages(messages);\n if (messages.length === 0) hostRef.current?.clearRenderCache();\n }, [messages]);\n\n // Reconcile bubbles whenever the rendered list changes (the host queries the\n // DOM for `<aparte-chat-bubble message-id>` elements and pushes segments).\n useEffect(() => { hostRef.current?.syncBubbles(); }, [renderMessages]);\n\n // Controlled typing indicator: reflect the prop, while the host may flip it\n // off internally on the first streamed token.\n useEffect(() => { setTypingActive(isTyping); }, [isTyping]);\n\n // Conversation id changes (the initial value is loaded by the host on bind).\n useEffect(() => {\n if (conversationId === lastConvRef.current) return;\n lastConvRef.current = conversationId;\n void hostRef.current?.setConversationId(conversationId ?? null);\n }, [conversationId]);\n\n // Surface composer sends to the consumer (the controller handles the\n // conversation side separately via its own host listener).\n useEffect(() => {\n const composer = composerRef.current;\n if (!composer) return;\n const onSend = (e: Event) => {\n (viewportRef.current as unknown as { requestSmoothScroll?: () => void })?.requestSmoothScroll?.();\n cbRef.current.onMessageSent?.((e as CustomEvent<AparteSendEventDetail>).detail);\n };\n composer.addEventListener('aparte-send', onSend);\n return () => composer.removeEventListener('aparte-send', onSend);\n }, []);\n\n // Set as ATTRIBUTES, because the attribute IS the state core's composer parts\n // read. React 19 assigns a matching prop as a PROPERTY on a custom element; core\n // gained the matching setters in 0.16.12 (before that these were getter-only and\n // the assignment threw, taking the render down), so this is now a direct write\n // rather than a way around a crash — and it is the only writer, since the JSX\n // below declares neither.\n useEffect(() => {\n const composer = composerRef.current;\n if (!composer) return;\n composer.setAttribute('placeholder', placeholder);\n if (disabled || waiting) composer.setAttribute('disabled', '');\n else composer.removeAttribute('disabled');\n }, [placeholder, disabled, waiting]);\n // Same story for the viewport's `loading`: React 19 would set it as a PROPERTY, and\n // `''` through the setter reads as false. The attribute is what the element reads.\n useEffect(() => {\n viewportRef.current?.toggleAttribute('loading', waiting);\n }, [waiting]);\n\n // Custom bubble actions bubble to the host root as `aparte-action`; surface them\n // as a typed prop.\n useEffect(() => {\n const host = hostElRef.current;\n if (!host) return;\n const onAct = (e: Event) => cbRef.current.onAction?.((e as CustomEvent<AparteActionEventDetail>).detail);\n host.addEventListener('aparte-action', onAct);\n return () => host.removeEventListener('aparte-action', onAct);\n }, []);\n\n useImperativeHandle(ref, (): AparteChatImperativeApi => ({\n appendMessage: (m, o) => hostRef.current?.appendMessage(m, o),\n updateMessage: (id, u) => hostRef.current?.updateMessage(id, u),\n updateLastMessage: (c, o) => hostRef.current?.updateLastMessage(c, o),\n addSegment: (s) => hostRef.current?.addSegment(s),\n updateSegment: (id, u) => hostRef.current?.updateSegment(id, u),\n removeSegment: (id) => hostRef.current?.removeSegment(id),\n appendToSegment: (id, c) => hostRef.current?.appendToSegment(id, c),\n getMessages: () => hostRef.current?.getMessages() ?? messagesRef.current,\n clearMessages: (o) => hostRef.current?.clearMessages(o),\n addBranch: (id) => hostRef.current?.addBranch(id) ?? 0,\n addSiblingOf: (id, m) => hostRef.current?.addSiblingOf(id, m) ?? null,\n truncateFrom: (id) => hostRef.current?.truncateFrom(id),\n truncateResponsesAfter: (id) => hostRef.current?.truncateResponsesAfter(id),\n injectTokenStream: (id, tokens) => hostRef.current?.streamTokens(id, tokens) ?? Promise.resolve(),\n stopTokenStream: () => hostRef.current?.stopTokenStream(),\n setConversationId: (id) => hostRef.current?.setConversationId(id) ?? Promise.resolve(),\n scrollToBottom: () => (viewportRef.current as unknown as { scrollToBottom?: () => void })?.scrollToBottom?.(),\n focusInput: () => (composerRef.current as unknown as { focus?: () => void })?.focus?.(),\n isStreaming: () => hostRef.current?.isStreaming ?? false,\n getViewport: () => viewportRef.current,\n }), []);\n\n return (\n <div\n className={`aparte-chat-container${centerWhenEmpty ? ' aparte-chat-container--auto-center' : ''}${className ? ` ${className}` : ''}`}\n style={style}\n data-aparte-chat=\"\"\n {...(overlayComposer ? { 'overlay-composer': '' } : {})}\n data-aparte-empty={centerWhenEmpty && renderMessages.length === 0 && !waiting ? '' : undefined}\n id={hostId}\n ref={hostElRef}\n >\n <aparte-chat-viewport ref={viewportRef as React.Ref<HTMLElement>} framework-managed=\"\">\n {/* The wait, drawn here because this DOM is React's: the kit's skeleton recipe, and a line for a screen reader. */}\n {waiting && (\n <div className=\"aparte-viewport-loading\" aria-hidden=\"true\">\n <span className=\"aparte-skeleton aparte-skeleton--rect aparte-viewport-loading__user\" />\n <span className=\"aparte-skeleton aparte-skeleton--text\" />\n <span className=\"aparte-skeleton aparte-skeleton--text\" />\n <span className=\"aparte-skeleton aparte-skeleton--text\" />\n <span className=\"aparte-skeleton aparte-skeleton--text aparte-viewport-loading__last\" />\n </div>\n )}\n {waiting && <span className=\"aparte-viewport-loading-status aparte-sr-only\" role=\"status\">{loadingText}</span>}\n {renderMessages.length === 0 && !waiting && emptyState}\n {renderMessages.map((m) => (\n renderBubble\n ? <React.Fragment key={m.id}>{renderBubble(m)}</React.Fragment>\n : (\n <aparte-chat-bubble\n key={m.id}\n message-id={m.id}\n data-role={m.role}\n data-kind={m.compaction ? 'compaction' : undefined}\n timestamp={m.timestamp}\n content={m.content}\n streaming={isAwaitingReply(m) ? '' : undefined}\n />\n )\n ))}\n <aparte-chat-status visible={typingActive ? '' : undefined} text={typingLabel} />\n </aparte-chat-viewport>\n\n {elicitation && <aparte-elicitation />}\n\n {aboveComposer}\n\n <aparte-composer\n ref={composerRef as React.Ref<HTMLElement>}\n target={hostId}\n submit-on-enter={submitOnEnter ? undefined : 'false'}\n >\n {composer ?? (\n <div className=\"aparte-composer-shell\">\n {attachments && <aparte-composer-attachments />}\n <div className=\"aparte-composer-row\">\n {attachments && <aparte-composer-add-attachment />}\n <aparte-composer-input />\n <aparte-composer-send />\n </div>\n {toolbar != null && (\n <aparte-composer-toolbar>{toolbar}</aparte-composer-toolbar>\n )}\n </div>\n )}\n </aparte-composer>\n </div>\n );\n});\n\nAparteChat.displayName = 'AparteChat';\n","import { useRef, useState } from 'react';\nimport type { AparteMessage, AparteSegment } from '../types.js';\nimport type { AparteChatImperativeApi } from '@aparte/core';\n\n/**\n * Idiomatic React ergonomics for `<AparteChat>`. Owns the `messages` state and\n * the component ref so the consumer doesn't have to wire `onMessagesChange`\n * back into `messages` by hand. Spread `ref` + `messages` + `onMessagesChange`\n * onto `<AparteChat>` and drive it with the returned imperative helpers.\n *\n * @example\n * const chat = useAparteChat();\n * return (\n * <AparteChat\n * ref={chat.ref}\n * messages={chat.messages}\n * onMessagesChange={chat.setMessages}\n * />\n * );\n * // The user's message is appended automatically on send — don't re-add it.\n */\nexport interface UseAparteChat {\n messages: AparteMessage[];\n setMessages: React.Dispatch<React.SetStateAction<AparteMessage[]>>;\n // Typed to match `<AparteChat>`'s forwardRef target so `ref={chat.ref}`\n // typechecks verbatim — @types/react 18.3's variance rejects the\n // useRef(null) `RefObject<T | null>` shape against `Ref<T>`, so the (safe)\n // cast lives here once, not in every consumer.\n ref: React.RefObject<AparteChatImperativeApi>;\n // ── imperative helpers (delegate to the component handle) ──\n appendMessage: (message: AparteMessage, options?: { historical?: boolean }) => void;\n updateMessage: (messageId: string, updates: Partial<AparteMessage>) => void;\n updateLastMessage: (content: string, options?: { append?: boolean }) => void;\n addSegment: (segment: AparteSegment) => void;\n updateSegment: (segmentId: string, updates: Partial<AparteSegment>) => void;\n removeSegment: (segmentId: string) => void;\n appendToSegment: (segmentId: string, content: string) => void;\n /**\n * The host's list — the authority, and mid-stream a frame ahead of the\n * `messages` state, which is synced once per paint rather than per token.\n */\n getMessages: () => AparteMessage[];\n clearMessages: (options?: { revokeAttachments?: boolean }) => void;\n addBranch: (messageId: string) => number;\n addSiblingOf: (existingId: string, message: AparteMessage) => string | null;\n truncateFrom: (messageId: string) => void;\n truncateResponsesAfter: (userMessageId: string) => void;\n injectTokenStream: (messageId: string, tokens: AsyncIterable<string>) => Promise<void>;\n stopTokenStream: () => void;\n setConversationId: (id: string | null) => Promise<void>;\n isStreaming: () => boolean;\n scrollToBottom: () => void;\n focusInput: () => void;\n /** The `<aparte-chat-viewport>` element — same accessor on all four wrappers. */\n getViewport: () => HTMLElement | null;\n}\n\nexport function useAparteChat(initial: AparteMessage[] = []): UseAparteChat {\n const [messages, setMessages] = useState<AparteMessage[]>(initial);\n const ref = useRef<AparteChatImperativeApi | null>(null);\n const h = () => ref.current;\n return {\n messages,\n setMessages,\n ref: ref as React.RefObject<AparteChatImperativeApi>,\n appendMessage: (m, o) => h()?.appendMessage(m, o),\n updateMessage: (id, u) => h()?.updateMessage(id, u),\n updateLastMessage: (c, o) => h()?.updateLastMessage(c, o),\n addSegment: (s) => h()?.addSegment(s),\n updateSegment: (id, u) => h()?.updateSegment(id, u),\n removeSegment: (id) => h()?.removeSegment(id),\n appendToSegment: (id, c) => h()?.appendToSegment(id, c),\n getMessages: () => h()?.getMessages() ?? messages,\n clearMessages: (o) => h()?.clearMessages(o),\n addBranch: (id) => h()?.addBranch(id) ?? 0,\n addSiblingOf: (id, m) => h()?.addSiblingOf(id, m) ?? null,\n truncateFrom: (id) => h()?.truncateFrom(id),\n truncateResponsesAfter: (id) => h()?.truncateResponsesAfter(id),\n injectTokenStream: (id, tokens) => h()?.injectTokenStream(id, tokens) ?? Promise.resolve(),\n stopTokenStream: () => h()?.stopTokenStream(),\n setConversationId: (id) => h()?.setConversationId(id) ?? Promise.resolve(),\n isStreaming: () => h()?.isStreaming() ?? false,\n scrollToBottom: () => h()?.scrollToBottom(),\n focusInput: () => h()?.focusInput(),\n getViewport: () => h()?.getViewport() ?? null,\n };\n}\n","import { useEffect, useRef } from 'react';\nimport { AparteClient, type AparteClientOptions } from '@aparte/core';\n\nexport interface UseAparteClient {\n /** The underlying agnostic client. */\n client: AparteClient;\n /** Abort the current AI response + all active tool calls. */\n abort: () => void;\n}\n\n/**\n * Mounts an `AparteClient` that bridges `aparte-send` events to the configured AI\n * providers. Starts listening on mount, stops on unmount. React equivalent of\n * Angular's `AparteAiService`.\n *\n * `options` is read ONCE, when the client is constructed on the first render. Later\n * changes are not applied — a `keyResolver` (or any other function option) that closes\n * over state keeps the closure it had on mount, so read the live value inside the\n * function (a ref) rather than expecting a new object to take effect. To swap options\n * wholesale, remount the component that owns the hook (`key`).\n *\n * @example\n * const { abort } = useAparteClient({ keyResolver });\n */\nexport function useAparteClient(options?: AparteClientOptions): UseAparteClient {\n const ref = useRef<AparteClient | null>(null);\n if (!ref.current) ref.current = new AparteClient(options ?? {});\n useEffect(() => {\n const c = ref.current as AparteClient;\n c.start();\n return () => c.stop();\n }, []);\n return { client: ref.current, abort: () => ref.current?.abort() };\n}\n","import { useEffect, useMemo, useRef, useState, useCallback } from 'react';\nimport {\n aparteGlobalConfig,\n type AparteConfig,\n AparteConversationManager,\n type AparteConversation,\n type AparteStorageAdapter,\n type ConversationManagerOptions,\n} from '@aparte/core';\nimport type { AparteMessage } from '../types.js';\n\nexport interface UseConversationManager {\n conversations: AparteConversation[];\n /** Active conversations, newest first. */\n activeConversations: AparteConversation[];\n /** Archived conversations, newest first. */\n archivedConversations: AparteConversation[];\n activeId: string | null;\n activeConversation: AparteConversation | null;\n /**\n * Initialise with a storage adapter (call once). `config` is the one you gave\n * `<AparteChat config={…}>`; it defaults to the global — the implementation always\n * took it, the interface hid it, so the documented two-argument call did not type.\n */\n init: (adapter: AparteStorageAdapter, config?: AparteConfig, options?: ConversationManagerOptions) => Promise<void>;\n /**\n * The core manager this hook wraps, once `init` has run (`null` before): what the hook\n * does not mirror — `setTitleProvider`, `ensureFull`, `loadAll` — is reached through it.\n */\n readonly manager: AparteConversationManager | null;\n createNew: (title?: string) => Promise<AparteConversation>;\n addMessage: (convId: string, message: AparteMessage) => Promise<void>;\n updateMessages: (convId: string, messages: AparteMessage[]) => Promise<void>;\n delete: (id: string) => Promise<void>;\n archive: (id: string) => Promise<void>;\n unarchive: (id: string) => Promise<void>;\n pin: (id: string) => Promise<void>;\n unpin: (id: string) => Promise<void>;\n updateTitle: (id: string, title: string) => Promise<void>;\n}\n\n/**\n * React-state wrapper around the core `AparteConversationManager`. The active\n * conversation is owned by the chat component's controller; switch by binding\n * `conversationId` on `<AparteChat>`. React equivalent of Angular's\n * `ConversationManagerService`.\n */\nexport function useConversationManager(): UseConversationManager {\n const managerRef = useRef<AparteConversationManager | null>(null);\n const unsubRef = useRef<(() => void) | null>(null);\n const [conversations, setConversations] = useState<AparteConversation[]>([]);\n const [activeId, setActiveId] = useState<string | null>(null);\n\n useEffect(() => () => unsubRef.current?.(), []);\n\n /**\n * Initialise with a storage adapter.\n *\n * `config` defaults to the global singleton. Pass the SAME config you gave\n * `<AparteChat config={…}>`: the controller resolves the config governing its\n * host element, so a manager registered on the global is invisible to a chat\n * with its own — persistence silently does nothing while the optimistic UI\n * keeps working.\n */\n const init = useCallback(async (adapter: AparteStorageAdapter, config: AparteConfig = aparteGlobalConfig, options?: ConversationManagerOptions) => {\n const m = new AparteConversationManager(adapter, options);\n managerRef.current = m;\n unsubRef.current = m.subscribe((convs) => {\n setConversations([...convs]);\n setActiveId(m.activeId);\n });\n await m.init();\n setActiveId(m.activeId);\n config.setConversationManager(m);\n }, []);\n\n const assert = (): AparteConversationManager => {\n if (!managerRef.current) {\n throw new Error('[useConversationManager] Not initialised. Call init(adapter) first.');\n }\n return managerRef.current;\n };\n\n const activeConversations = useMemo(\n () => conversations.filter((c) => !c.archivedAt).sort((a, b) => b.updatedAt - a.updatedAt),\n [conversations],\n );\n const archivedConversations = useMemo(\n () => conversations.filter((c) => !!c.archivedAt).sort((a, b) => b.updatedAt - a.updatedAt),\n [conversations],\n );\n const activeConversation = useMemo(\n () => (activeId ? conversations.find((c) => c.id === activeId) ?? null : null),\n [conversations, activeId],\n );\n\n return {\n conversations,\n activeConversations,\n archivedConversations,\n activeId,\n activeConversation,\n init,\n get manager() { return managerRef.current; },\n createNew: (title) => assert().createNew(title),\n addMessage: (convId, message) => assert().addMessage(convId, message),\n updateMessages: (convId, messages) => assert().updateMessages(convId, messages),\n delete: (id) => assert().delete(id),\n archive: (id) => assert().archive(id),\n unarchive: (id) => assert().unarchive(id),\n pin: (id) => assert().pin(id),\n unpin: (id) => assert().unpin(id),\n updateTitle: (id, title) => assert().updateTitle(id, title),\n };\n}\n","import { useEffect, useRef, forwardRef, useImperativeHandle } from 'react';\nimport { applyElementProps, APARTE_DEFAULT_UI_EVENTS, type AparteUiHandle } from '@aparte/core';\n\nexport interface AparteUiProps {\n /** The custom element tag name (e.g. 'aparte-model-selector'). */\n name: string;\n /** Props to apply. Keys starting with `--` become CSS variables. */\n props?: Record<string, unknown>;\n /** Emits a forwarded custom event from the underlying Web Component. */\n onElementEvent?: (event: CustomEvent) => void;\n /**\n * Which custom events to forward through `onElementEvent`. Defaults to the\n * interactive aparté surface ({@link APARTE_DEFAULT_UI_EVENTS}); pass your own list to\n * listen to other events (e.g. `['aparte-composer-change']` for attachments).\n */\n events?: string[];\n}\n\n// The handle is core's, not this wrapper's: the same two-method contract was\n// hand-copied into all four, each docblock promising \"the same on all four\", and it\n// had already drifted (two of them returned `HTMLElement | null` / `unknown` instead\n// of the generic form). One declaration in `@aparte/core`, four re-exports.\nexport type { AparteUiHandle } from '@aparte/core';\n\n/**\n * Universal pass-through proxy: dynamically mounts any `aparte-*` Web Component so\n * you don't need a dedicated React wrapper per element. React equivalent of\n * Angular's `AparteUiComponent`.\n *\n * `name` can be any custom element, including one of yours. `aparte-model-selector`\n * below is defined by `@aparte/plugin-model-selector`, not by core — until that\n * package is imported the tag mounts empty and inert with no error, and upgrades on\n * its own once the definition arrives (which is what makes lazy-loading it work).\n *\n * A PLUGIN's event is not in the default forward list — that default is core's, and a\n * third-party plugin's author cannot add to it, so listing ours would privilege our\n * packages over theirs. Name it in `events` and it arrives; leave it out and the handler\n * silently never fires, which is what this example used to do.\n *\n * @example\n * <AparteUi\n * name=\"aparte-model-selector\"\n * props={{ placeholder: 'Ask…', '--glow-speed': '4s' }}\n * events={['aparte-model-change']}\n * onElementEvent={onEvent}\n * />\n */\nexport const AparteUi = forwardRef<AparteUiHandle, AparteUiProps>(function AparteUi(\n { name, props = {}, onElementEvent, events },\n ref,\n) {\n const hostRef = useRef<HTMLSpanElement>(null);\n const elRef = useRef<HTMLElement | null>(null);\n const cbRef = useRef(onElementEvent);\n cbRef.current = onElementEvent;\n const propsRef = useRef(props);\n propsRef.current = props;\n\n const evts = events ?? APARTE_DEFAULT_UI_EVENTS;\n const evtsKey = evts.join('|');\n\n // (Re)create the element when `name` (or the forwarded event set) changes.\n useEffect(() => {\n const host = hostRef.current;\n if (!host) return;\n const el = document.createElement(name);\n elRef.current = el;\n // The props effect below compares `props` by identity, so a memoized prop bag\n // never re-applies on a `name`/`events` change. Apply through a ref here — the\n // same shape Vue/Svelte/Angular use inside their own create().\n applyElementProps(el, propsRef.current);\n const cleanups: Array<() => void> = [];\n for (const ev of evtsKey.split('|').filter(Boolean)) {\n const listener = (e: Event) => cbRef.current?.(e as CustomEvent);\n el.addEventListener(ev, listener);\n cleanups.push(() => el.removeEventListener(ev, listener));\n }\n host.appendChild(el);\n return () => {\n for (const c of cleanups) c();\n el.remove();\n elRef.current = null;\n };\n }, [name, evtsKey]);\n\n // Apply props whenever they change.\n useEffect(() => {\n if (elRef.current) applyElementProps(elRef.current, props);\n }, [props]);\n\n useImperativeHandle(ref, (): AparteUiHandle => ({\n getElement: () => elRef.current as never,\n callMethod: (methodName, ...args) => {\n const fn = (elRef.current as unknown as Record<string, unknown>)?.[methodName];\n return typeof fn === 'function'\n ? (fn as (...a: unknown[]) => unknown).apply(elRef.current, args) as never\n : undefined;\n },\n }), []);\n\n return <span ref={hostRef} style={{ display: 'contents' }} />;\n});\n\nAparteUi.displayName = 'AparteUi';\n"],"names":["AparteChat","composer","AparteUi"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/internal/after-render.ts","../src/components/AparteChat.tsx","../src/hooks/useAparteChat.ts","../src/hooks/useAparteClient.ts","../src/hooks/useConversationManager.ts","../src/components/AparteUi.tsx","../src/components/AparteToolUI.tsx"],"sourcesContent":["/**\n * Run `cb` once React has committed the render in flight: on the next animation frame — or,\n * in a hidden tab, on the next task, since a hidden document runs no animation frame while\n * React still commits there. Waiting on a frame there held a compaction's save until the\n * reader came back, and lost it if they closed the tab first.\n */\nexport function afterReactRender(cb: () => void): void {\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') setTimeout(cb, 0);\n else requestAnimationFrame(() => cb());\n}\n","'use client';\n\nimport React, {\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n useSyncExternalStore,\n forwardRef,\n useImperativeHandle,\n} from 'react';\nimport { afterReactRender } from '../internal/after-render.js';\nimport { AparteChatHost, aparteGlobalConfig, isAwaitingReply, releaseDeferredHydration, type AparteChatHostBinding, type AparteConfig, type AparteChatImperativeApi } from '@aparte/core';\nimport type { AparteMessage, AparteSendEventDetail, AparteActionEventDetail } from '../types.js';\n\nexport interface AparteChatProps {\n /**\n * The host element's `id`, and therefore the `targetId` every event this chat\n * dispatches carries.\n *\n * Generated when omitted, which is the right default — but it used to be\n * generated and neither accepted nor exposed, so\n * `AparteClientOptions.scopeToTargetId` (the documented way to run several\n * independent clients on one page) was unreachable from this component: there\n * was no way to learn the id the client had to match. Angular exposed it; React,\n * Vue and Svelte did not.\n */\n id?: string;\n\n /**\n * Messages on the active path. **Optional** — omit for an uncontrolled chat\n * that starts empty (defaults to `[]`); pass it together with\n * `onMessagesChange` to control the list from the parent.\n */\n messages?: AparteMessage[];\n placeholder?: string;\n disabled?: boolean;\n isTyping?: boolean;\n typingText?: string;\n /** When false, Shift+Enter submits and a bare Enter inserts a newline. */\n submitOnEnter?: boolean;\n /** Freeze viewport spacer recalculation for this many ms after a conv swap. */\n layoutTransitionMs?: number;\n /**\n * Opt in to the \"centered composer when empty\" layout: while the message\n * list is empty the composer sits vertically centered with the `emptyState`\n * content above it, then slides to the bottom on the first message (~0.3s).\n * Off by default — purely additive: adds the `aparte-chat-container--auto-center`\n * modifier + a `data-aparte-empty` attribute that the shipped `aparte.css` recipe\n * keys off. No effect unless you also render an `emptyState`.\n */\n centerWhenEmpty?: boolean;\n /**\n * The conversation is on its way: the viewport draws two skeleton turns, the empty\n * state stays off and the composer is disabled until it lands. The conversation\n * controller sets this itself while it fetches through your adapter's `loadFull()`;\n * the prop is for a wait of your own. Off by default.\n */\n loading?: boolean;\n /**\n * Overlay the composer on the transcript (the ChatGPT anatomy): the scroll\n * surface spans the whole column, the scrollbar runs edge to edge, and the\n * composer floats over it. Core measures the floating stack and keeps content,\n * spacer and the scroll button clear of it. Read when the viewport mounts.\n */\n overlayComposer?: boolean;\n /**\n * Add the file picker + chips strip to the default composer shell. **Off by\n * default**, because the capability needs a host that consumes the files: an\n * `AparteClient` inlines them per its `rawFileInject` option, but if you drive\n * your own loop you must read `event.files` in `onMessageSent` — otherwise the\n * file the user deliberately attached is dropped in silence. No effect when you\n * pass your own `composer` (drop `<aparte-composer-add-attachment>` and\n * `<aparte-composer-attachments>` in it yourself).\n */\n attachments?: boolean;\n /**\n * Render the `<aparte-elicitation>` presenter inside the host — **on by default**,\n * as it is in `<aparte-chat>`: the built-in approval gate and `requestUserInput()`\n * ask through it, and it renders nothing until something asks. Pass `false` when\n * you mount a presenter of your own (`setElicitationPresenter`) or place the element\n * yourself. The first consumer to hit this appended the element by hand next to the\n * React tree, guided by a warning that named an `<aparte-chat>` this wrapper does not\n * render.\n */\n elicitation?: boolean;\n /** Extra class names for the root element (`[data-aparte-chat]`), merged after core's own. */\n className?: string;\n /** Inline style for the root element (`[data-aparte-chat]`). */\n style?: React.CSSProperties;\n /**\n * Active conversation id. When set, the wrapper loads/persists via the\n * `AparteConversationManager` registered in `aparteGlobalConfig` (set `null` to deselect).\n */\n conversationId?: string | null;\n /**\n * Custom composer content, rendered inside `<aparte-composer>` in place of the\n * default shell (input · send, plus the attachment primitives when\n * `attachments` is set). Compose the headless\n * `aparte-composer-*` primitives freely — e.g. a skin-specific layout. Omit for\n * the default shell. The `<aparte-composer>` element (and its placeholder /\n * disabled / submit-on-enter behaviour) is always provided by the wrapper.\n */\n composer?: React.ReactNode;\n /**\n * Render your OWN element per message in place of `<aparte-chat-bubble>`.\n * Opt-in — omit for the default bubble. The returned node is driven by the\n * reactive message list, so it updates live during streaming (no need to\n * implement any imperative interface): re-render from `message.content` /\n * `message.segments`. Note the built-in action bar (retry/edit/branch) and\n * the imperative streaming push are the native bubble's — a custom bubble\n * owns whatever it wires (it can dispatch `aparte-retry` etc. or call the\n * wrapper's imperative API).\n */\n renderBubble?: (message: AparteMessage) => React.ReactNode;\n /**\n * Welcome / placeholder content shown INSIDE the viewport while there are no\n * messages (a real \"empty state\" region, not a workaround via `aboveComposer`).\n * Replaced by the message list on the first message.\n */\n emptyState?: React.ReactNode;\n /**\n * Content rendered ABOVE the composer (e.g. a disclaimer banner, a\n * \"scroll to bottom\" affordance, a context chip). Ignored when a full\n * custom `composer` replaces the shell.\n */\n aboveComposer?: React.ReactNode;\n /**\n * The composer's bottom row — a mode picker, a model selector, a token counter.\n * Rendered inside core's `<aparte-composer-toolbar>`; nothing is rendered at all\n * when you pass nothing, so an unused row never draws its separator.\n *\n * **Placement is the DOM order.** There is no left/center/right slot: put things in\n * the order you want them, and push a control (with everything after it) to the end\n * with `margin-inline-start: auto`. That is a logical property, so the row reads\n * correctly in a right-to-left locale with no extra work.\n *\n * Ignored when a full custom `composer` replaces the shell.\n *\n * @example\n * <AparteChat\n * messages={messages}\n * onMessagesChange={setMessages}\n * toolbar={<>\n * <ModePicker value={mode} onChange={setMode} />\n * <ModelSelector style={{ marginInlineStart: 'auto' }} />\n * </>}\n * />\n */\n toolbar?: React.ReactNode;\n\n /**\n * Notification that the user submitted a message from the composer. The\n * user's message is **appended to the thread automatically** (optimistic UI)\n * before this fires — do NOT add it again here. In an uncontrolled chat,\n * appending it duplicates it; in a controlled chat, mirror it into your own\n * `messages`. Use this for side-effects: scroll, analytics, backend send.\n */\n onMessageSent?: (event: AparteSendEventDetail) => void;\n /**\n * Fired when a custom bubble action (registered via\n * `aparteGlobalConfig.registerAction` with `zones: ['bubble']`) is clicked — a typed\n * wrapper over the bubbling `aparte-action` DOM event. Dispatch on `detail.actionId`.\n */\n onAction?: (detail: AparteActionEventDetail) => void;\n /**\n * Fired when the active message path changes (branch navigation / edit /\n * retry / streaming). Set the result back as the `messages` prop.\n */\n onMessagesChange?: (messages: AparteMessage[]) => void;\n /** Fired when a message is appended internally (e.g. by AparteClient). */\n onMessageAppended?: (message: AparteMessage) => void;\n /** Fired when the typing/\"thinking\" indicator should toggle. */\n onTypingChange?: (isTyping: boolean) => void;\n /** Fired when the controller lazily creates a conversation on first send. */\n onConversationCreated?: (id: string) => void;\n\n /**\n * Instance {@link AparteConfig} for this chat. When set, every aparté\n * component rendered inside resolves THIS config instead of the global\n * `aparteGlobalConfig` — letting several independently-configured chats\n * (different providers, tools, renderers) coexist on one page. Omit for the\n * global config. Read once when the host mounts.\n */\n config?: AparteConfig;\n}\n\n/**\n * The omitted-prop default, hoisted out of the render: a destructuring default\n * `messages = []` is a NEW array on every render, and the parent-push effect below\n * compares by identity — so an uncontrolled `<AparteChat>` applied an empty list on\n * every render, wiping its own thread, and looped. One module-level array is safe to\n * share across instances: it is never mutated (the host replaces the list).\n */\nconst NO_MESSAGES: AparteMessage[] = [];\n\n/** \"Am I hydrating?\" as a store React reads: its server snapshot is used for the hydration render. */\nconst subscribeToNothing = () => () => {};\nconst isHydrating = () => true;\nconst isNotHydrating = () => false;\n\nexport const AparteChat = forwardRef<AparteChatImperativeApi, AparteChatProps>(function AparteChat(\n {\n messages = NO_MESSAGES,\n placeholder = 'Type a message...',\n disabled = false,\n isTyping = false,\n typingText,\n submitOnEnter = true,\n layoutTransitionMs = 0,\n centerWhenEmpty = false,\n loading = false,\n overlayComposer = false,\n attachments = false,\n elicitation = true,\n className,\n style,\n conversationId = null,\n composer,\n renderBubble,\n emptyState,\n aboveComposer,\n toolbar,\n config,\n onMessageSent,\n onAction,\n onMessagesChange,\n onMessageAppended,\n onTypingChange,\n onConversationCreated,\n id: providedId,\n },\n ref,\n) {\n // useId() is SSR-stable (server and client agree), so no hydration mismatch.\n // Strip ':' so the id is also safe in CSS/querySelector, not just getElementById.\n // A caller-supplied id wins, so `scopeToTargetId` has something to match.\n const generatedId = `aparte-chat-${useId().replace(/:/g, '')}`;\n const hostId = providedId ?? generatedId;\n const hostElRef = useRef<HTMLDivElement>(null);\n // True in the server render and in the render that hydrates it, false in every client render\n // after — and from the first one in an app with no server render. While true the root carries\n // `defer-hydration`, so core's elements leave the server's DOM alone until React has it.\n const hydrating = useSyncExternalStore(subscribeToNothing, isNotHydrating, isHydrating);\n const viewportRef = useRef<HTMLElement>(null);\n const composerRef = useRef<HTMLElement>(null);\n\n // Authoritative message list lives in a ref (the host reads it synchronously);\n // `renderMessages` state drives the declarative bubble list.\n const messagesRef = useRef<AparteMessage[]>(messages);\n const [renderMessages, setRenderMessages] = useState<AparteMessage[]>(messages);\n const [typingActive, setTypingActive] = useState(isTyping);\n // The controller's wait (a conversation fetched through the adapter) OR the consumer's.\n const [hostLoading, setHostLoading] = useState(false);\n const waiting = loading || hostLoading;\n // The two locale strings this component draws itself, read again on a language switch: a\n // config change re-renders nothing else here. One snapshot, so only a changed string re-renders.\n const labelConfig = config ?? aparteGlobalConfig;\n const readLabels = (): string => `${labelConfig.t('loadingConversation')}\\u0000${labelConfig.t('typing')}`;\n const labels = useSyncExternalStore(useCallback((notify) => labelConfig.subscribe(notify), [labelConfig]), readLabels, readLabels);\n const [loadingText = '', localeTyping = ''] = labels.split('\\u0000');\n // The status line's word, from the locale like the rest of the chat, unless the prop says.\n const typingLabel = typingText ?? localeTyping;\n const [, setIsStreaming] = useState(false);\n\n const hostRef = useRef<AparteChatHost | null>(null);\n const lastConvRef = useRef<string | null>(conversationId);\n\n // Keep the latest prop callbacks in a ref so the host's stable binding always\n // calls the current handlers without recreating the host.\n const cbRef = useRef({ onMessageSent, onAction, onMessagesChange, onMessageAppended, onTypingChange, onConversationCreated });\n cbRef.current = { onMessageSent, onAction, onMessagesChange, onMessageAppended, onTypingChange, onConversationCreated };\n\n const applyMessages = (m: AparteMessage[]) => {\n messagesRef.current = m;\n setRenderMessages(m);\n };\n\n // Create the host once after mount — after the elements a server render left waiting are\n // upgraded, since the host talks to them.\n useEffect(() => {\n const host = hostElRef.current;\n if (!host) return;\n releaseDeferredHydration(host);\n const binding: AparteChatHostBinding = {\n hostId,\n host,\n viewport: viewportRef.current,\n getMessages: () => messagesRef.current,\n setMessages: (m) => applyMessages(m as AparteMessage[]),\n onMessagesChange: (m) => cbRef.current.onMessagesChange?.(m as AparteMessage[]),\n onMessageAppended: (m) => cbRef.current.onMessageAppended?.(m as AparteMessage),\n onTypingChange: (t) => { setTypingActive(t); cbRef.current.onTypingChange?.(t); },\n onStreamingChange: (id) => setIsStreaming(id !== null),\n afterRender: afterReactRender,\n resetComposer: () => (composerRef.current as unknown as { reset?: () => void })?.reset?.(),\n onLoadingChange: (on) => setHostLoading(on),\n };\n const h = new AparteChatHost(binding, {\n layoutTransitionMs,\n conversationId: conversationId ?? null,\n onConversationCreated: (id) => cbRef.current.onConversationCreated?.(id),\n config,\n });\n hostRef.current = h;\n const teardown = h.bind();\n return () => { teardown(); hostRef.current = null; };\n // The host is created once per mount (keyed by the stable hostId); prop\n // changes flow through cbRef / dedicated effects, not by recreating it.\n }, [hostId]);\n\n // Parent push: sync the prop into the authoritative list — unless it is the host's own\n // report coming back. In controlled mode the prop comes back as one of them — and, when\n // the parent's render lands after the host's next write (WebKit commits React after the\n // host's frame), as an EARLIER one. An echo is not the parent's word: applying it rolled\n // the list back, the host built its next writes on it, and a finished reply lost its text\n // and its tool rows. The host tells them apart (`takeEcho`): asked first, always, so a\n // list the parent restores later is not mistaken for one.\n useEffect(() => {\n const echo = hostRef.current?.takeEcho(messages) ?? false;\n if (echo || messages === messagesRef.current) return;\n applyMessages(messages);\n if (messages.length === 0) hostRef.current?.clearRenderCache();\n }, [messages]);\n\n // Reconcile bubbles whenever the rendered list changes (the host queries the\n // DOM for `<aparte-chat-bubble message-id>` elements and pushes segments).\n useEffect(() => { hostRef.current?.syncBubbles(); }, [renderMessages]);\n\n // Controlled typing indicator: reflect the prop, while the host may flip it\n // off internally on the first streamed token.\n useEffect(() => { setTypingActive(isTyping); }, [isTyping]);\n\n // Conversation id changes (the initial value is loaded by the host on bind).\n useEffect(() => {\n if (conversationId === lastConvRef.current) return;\n lastConvRef.current = conversationId;\n void hostRef.current?.setConversationId(conversationId ?? null);\n }, [conversationId]);\n\n // Surface composer sends to the consumer (the controller handles the\n // conversation side separately via its own host listener).\n useEffect(() => {\n const composer = composerRef.current;\n if (!composer) return;\n const onSend = (e: Event) => {\n (viewportRef.current as unknown as { requestSmoothScroll?: () => void })?.requestSmoothScroll?.();\n cbRef.current.onMessageSent?.((e as CustomEvent<AparteSendEventDetail>).detail);\n };\n composer.addEventListener('aparte-send', onSend);\n return () => composer.removeEventListener('aparte-send', onSend);\n }, []);\n\n // Set as ATTRIBUTES, because the attribute IS the state core's composer parts\n // read. React 19 assigns a matching prop as a PROPERTY on a custom element; core\n // gained the matching setters in 0.16.12 (before that these were getter-only and\n // the assignment threw, taking the render down), so this is now a direct write\n // rather than a way around a crash — and it is the only writer, since the JSX\n // below declares neither.\n useEffect(() => {\n const composer = composerRef.current;\n if (!composer) return;\n composer.setAttribute('placeholder', placeholder);\n if (disabled || waiting) composer.setAttribute('disabled', '');\n else composer.removeAttribute('disabled');\n }, [placeholder, disabled, waiting]);\n // Same story for the viewport's `loading`: React 19 would set it as a PROPERTY, and\n // `''` through the setter reads as false. The attribute is what the element reads.\n useEffect(() => {\n viewportRef.current?.toggleAttribute('loading', waiting);\n }, [waiting]);\n\n // Custom bubble actions bubble to the host root as `aparte-action`; surface them\n // as a typed prop.\n useEffect(() => {\n const host = hostElRef.current;\n if (!host) return;\n const onAct = (e: Event) => cbRef.current.onAction?.((e as CustomEvent<AparteActionEventDetail>).detail);\n host.addEventListener('aparte-action', onAct);\n return () => host.removeEventListener('aparte-action', onAct);\n }, []);\n\n useImperativeHandle(ref, (): AparteChatImperativeApi => ({\n appendMessage: (m, o) => hostRef.current?.appendMessage(m, o),\n updateMessage: (id, u) => hostRef.current?.updateMessage(id, u),\n updateLastMessage: (c, o) => hostRef.current?.updateLastMessage(c, o),\n addSegment: (s) => hostRef.current?.addSegment(s),\n updateSegment: (id, u) => hostRef.current?.updateSegment(id, u),\n removeSegment: (id) => hostRef.current?.removeSegment(id),\n appendToSegment: (id, c) => hostRef.current?.appendToSegment(id, c),\n getMessages: () => hostRef.current?.getMessages() ?? messagesRef.current,\n clearMessages: (o) => hostRef.current?.clearMessages(o),\n addBranch: (id) => hostRef.current?.addBranch(id) ?? 0,\n addSiblingOf: (id, m) => hostRef.current?.addSiblingOf(id, m) ?? null,\n truncateFrom: (id) => hostRef.current?.truncateFrom(id),\n truncateResponsesAfter: (id) => hostRef.current?.truncateResponsesAfter(id),\n injectTokenStream: (id, tokens) => hostRef.current?.streamTokens(id, tokens) ?? Promise.resolve(),\n stopTokenStream: () => hostRef.current?.stopTokenStream(),\n setConversationId: (id) => hostRef.current?.setConversationId(id) ?? Promise.resolve(),\n scrollToBottom: () => (viewportRef.current as unknown as { scrollToBottom?: () => void })?.scrollToBottom?.(),\n focusInput: () => (composerRef.current as unknown as { focus?: () => void })?.focus?.(),\n isStreaming: () => hostRef.current?.isStreaming ?? false,\n getViewport: () => viewportRef.current,\n }), []);\n\n return (\n <div\n className={`aparte-chat-container${centerWhenEmpty ? ' aparte-chat-container--auto-center' : ''}${className ? ` ${className}` : ''}`}\n style={style}\n data-aparte-chat=\"\"\n {...(hydrating ? { 'defer-hydration': '' } : {})}\n {...(overlayComposer ? { 'overlay-composer': '' } : {})}\n data-aparte-empty={centerWhenEmpty && renderMessages.length === 0 && !waiting ? '' : undefined}\n id={hostId}\n ref={hostElRef}\n >\n <aparte-chat-viewport ref={viewportRef as React.Ref<HTMLElement>} framework-managed=\"\">\n {/* The wait, drawn here because this DOM is React's: the kit's skeleton recipe, and a line for a screen reader. */}\n {waiting && (\n <div className=\"aparte-viewport-loading\" aria-hidden=\"true\">\n <span className=\"aparte-skeleton aparte-skeleton--rect aparte-viewport-loading__user\" />\n <span className=\"aparte-skeleton aparte-skeleton--text\" />\n <span className=\"aparte-skeleton aparte-skeleton--text\" />\n <span className=\"aparte-skeleton aparte-skeleton--text\" />\n <span className=\"aparte-skeleton aparte-skeleton--text aparte-viewport-loading__last\" />\n </div>\n )}\n {waiting && <span className=\"aparte-viewport-loading-status aparte-sr-only\" role=\"status\">{loadingText}</span>}\n {renderMessages.length === 0 && !waiting && emptyState}\n {renderMessages.map((m) => (\n renderBubble\n ? <React.Fragment key={m.id}>{renderBubble(m)}</React.Fragment>\n : (\n <aparte-chat-bubble\n key={m.id}\n message-id={m.id}\n data-role={m.role}\n data-kind={m.compaction ? 'compaction' : undefined}\n timestamp={m.timestamp}\n content={m.content}\n streaming={isAwaitingReply(m) ? '' : undefined}\n />\n )\n ))}\n <aparte-chat-status visible={typingActive ? '' : undefined} text={typingLabel} />\n </aparte-chat-viewport>\n\n {elicitation && <aparte-elicitation />}\n\n {aboveComposer}\n\n <aparte-composer\n ref={composerRef as React.Ref<HTMLElement>}\n target={hostId}\n submit-on-enter={submitOnEnter ? undefined : 'false'}\n >\n {composer ?? (\n <div className=\"aparte-composer-shell\">\n {attachments && <aparte-composer-attachments />}\n <div className=\"aparte-composer-row\">\n {attachments && <aparte-composer-add-attachment />}\n <aparte-composer-input />\n <aparte-composer-send />\n </div>\n {toolbar != null && (\n <aparte-composer-toolbar>{toolbar}</aparte-composer-toolbar>\n )}\n </div>\n )}\n </aparte-composer>\n </div>\n );\n});\n\nAparteChat.displayName = 'AparteChat';\n","import { useRef, useState } from 'react';\nimport type { AparteMessage, AparteSegment } from '../types.js';\nimport type { AparteChatImperativeApi } from '@aparte/core';\n\n/**\n * Idiomatic React ergonomics for `<AparteChat>`. Owns the `messages` state and\n * the component ref so the consumer doesn't have to wire `onMessagesChange`\n * back into `messages` by hand. Spread `ref` + `messages` + `onMessagesChange`\n * onto `<AparteChat>` and drive it with the returned imperative helpers.\n *\n * @example\n * const chat = useAparteChat();\n * return (\n * <AparteChat\n * ref={chat.ref}\n * messages={chat.messages}\n * onMessagesChange={chat.setMessages}\n * />\n * );\n * // The user's message is appended automatically on send — don't re-add it.\n */\nexport interface UseAparteChat {\n messages: AparteMessage[];\n setMessages: React.Dispatch<React.SetStateAction<AparteMessage[]>>;\n // Typed to match `<AparteChat>`'s forwardRef target so `ref={chat.ref}`\n // typechecks verbatim — @types/react 18.3's variance rejects the\n // useRef(null) `RefObject<T | null>` shape against `Ref<T>`, so the (safe)\n // cast lives here once, not in every consumer.\n ref: React.RefObject<AparteChatImperativeApi>;\n // ── imperative helpers (delegate to the component handle) ──\n appendMessage: (message: AparteMessage, options?: { historical?: boolean }) => void;\n updateMessage: (messageId: string, updates: Partial<AparteMessage>) => void;\n updateLastMessage: (content: string, options?: { append?: boolean }) => void;\n addSegment: (segment: AparteSegment) => void;\n updateSegment: (segmentId: string, updates: Partial<AparteSegment>) => void;\n removeSegment: (segmentId: string) => void;\n appendToSegment: (segmentId: string, content: string) => void;\n /**\n * The host's list — the authority, and mid-stream a frame ahead of the\n * `messages` state, which is synced once per paint rather than per token.\n */\n getMessages: () => AparteMessage[];\n clearMessages: (options?: { revokeAttachments?: boolean }) => void;\n addBranch: (messageId: string) => number;\n addSiblingOf: (existingId: string, message: AparteMessage) => string | null;\n truncateFrom: (messageId: string) => void;\n truncateResponsesAfter: (userMessageId: string) => void;\n injectTokenStream: (messageId: string, tokens: AsyncIterable<string>) => Promise<void>;\n stopTokenStream: () => void;\n setConversationId: (id: string | null) => Promise<void>;\n isStreaming: () => boolean;\n scrollToBottom: () => void;\n focusInput: () => void;\n /** The `<aparte-chat-viewport>` element — same accessor on all four wrappers. */\n getViewport: () => HTMLElement | null;\n}\n\nexport function useAparteChat(initial: AparteMessage[] = []): UseAparteChat {\n const [messages, setMessages] = useState<AparteMessage[]>(initial);\n const ref = useRef<AparteChatImperativeApi | null>(null);\n const h = () => ref.current;\n return {\n messages,\n setMessages,\n ref: ref as React.RefObject<AparteChatImperativeApi>,\n appendMessage: (m, o) => h()?.appendMessage(m, o),\n updateMessage: (id, u) => h()?.updateMessage(id, u),\n updateLastMessage: (c, o) => h()?.updateLastMessage(c, o),\n addSegment: (s) => h()?.addSegment(s),\n updateSegment: (id, u) => h()?.updateSegment(id, u),\n removeSegment: (id) => h()?.removeSegment(id),\n appendToSegment: (id, c) => h()?.appendToSegment(id, c),\n getMessages: () => h()?.getMessages() ?? messages,\n clearMessages: (o) => h()?.clearMessages(o),\n addBranch: (id) => h()?.addBranch(id) ?? 0,\n addSiblingOf: (id, m) => h()?.addSiblingOf(id, m) ?? null,\n truncateFrom: (id) => h()?.truncateFrom(id),\n truncateResponsesAfter: (id) => h()?.truncateResponsesAfter(id),\n injectTokenStream: (id, tokens) => h()?.injectTokenStream(id, tokens) ?? Promise.resolve(),\n stopTokenStream: () => h()?.stopTokenStream(),\n setConversationId: (id) => h()?.setConversationId(id) ?? Promise.resolve(),\n isStreaming: () => h()?.isStreaming() ?? false,\n scrollToBottom: () => h()?.scrollToBottom(),\n focusInput: () => h()?.focusInput(),\n getViewport: () => h()?.getViewport() ?? null,\n };\n}\n","import { useEffect, useRef } from 'react';\nimport { AparteClient, type AparteClientOptions } from '@aparte/core';\n\nexport interface UseAparteClient {\n /** The underlying agnostic client. */\n client: AparteClient;\n /** Abort the current AI response + all active tool calls. */\n abort: () => void;\n}\n\n/**\n * Mounts an `AparteClient` that bridges `aparte-send` events to the configured AI\n * providers. Starts listening on mount, stops on unmount. React equivalent of\n * Angular's `AparteAiService`.\n *\n * `options` is read ONCE, when the client is constructed on the first render. Later\n * changes are not applied — a `keyResolver` (or any other function option) that closes\n * over state keeps the closure it had on mount, so read the live value inside the\n * function (a ref) rather than expecting a new object to take effect. To swap options\n * wholesale, remount the component that owns the hook (`key`).\n *\n * @example\n * const { abort } = useAparteClient({ keyResolver });\n */\nexport function useAparteClient(options?: AparteClientOptions): UseAparteClient {\n const ref = useRef<AparteClient | null>(null);\n if (!ref.current) ref.current = new AparteClient(options ?? {});\n useEffect(() => {\n const c = ref.current as AparteClient;\n c.start();\n return () => c.stop();\n }, []);\n return { client: ref.current, abort: () => ref.current?.abort() };\n}\n","import { useEffect, useMemo, useRef, useState, useCallback } from 'react';\nimport {\n aparteGlobalConfig,\n type AparteConfig,\n AparteConversationManager,\n type AparteConversation,\n type AparteStorageAdapter,\n type ConversationManagerOptions,\n} from '@aparte/core';\nimport type { AparteMessage } from '../types.js';\n\nexport interface UseConversationManager {\n conversations: AparteConversation[];\n /** Active conversations, newest first. */\n activeConversations: AparteConversation[];\n /** Archived conversations, newest first. */\n archivedConversations: AparteConversation[];\n activeId: string | null;\n activeConversation: AparteConversation | null;\n /**\n * Initialise with a storage adapter (call once). `config` is the one you gave\n * `<AparteChat config={…}>`; it defaults to the global — the implementation always\n * took it, the interface hid it, so the documented two-argument call did not type.\n */\n init: (adapter: AparteStorageAdapter, config?: AparteConfig, options?: ConversationManagerOptions) => Promise<void>;\n /**\n * The core manager this hook wraps, once `init` has run (`null` before): what the hook\n * does not mirror — `setTitleProvider`, `ensureFull`, `loadAll` — is reached through it.\n */\n readonly manager: AparteConversationManager | null;\n createNew: (title?: string) => Promise<AparteConversation>;\n addMessage: (convId: string, message: AparteMessage) => Promise<void>;\n updateMessages: (convId: string, messages: AparteMessage[]) => Promise<void>;\n delete: (id: string) => Promise<void>;\n archive: (id: string) => Promise<void>;\n unarchive: (id: string) => Promise<void>;\n pin: (id: string) => Promise<void>;\n unpin: (id: string) => Promise<void>;\n updateTitle: (id: string, title: string) => Promise<void>;\n}\n\n/**\n * React-state wrapper around the core `AparteConversationManager`. The active\n * conversation is owned by the chat component's controller; switch by binding\n * `conversationId` on `<AparteChat>`. React equivalent of Angular's\n * `ConversationManagerService`.\n */\nexport function useConversationManager(): UseConversationManager {\n const managerRef = useRef<AparteConversationManager | null>(null);\n const unsubRef = useRef<(() => void) | null>(null);\n const [conversations, setConversations] = useState<AparteConversation[]>([]);\n const [activeId, setActiveId] = useState<string | null>(null);\n\n useEffect(() => () => unsubRef.current?.(), []);\n\n /**\n * Initialise with a storage adapter.\n *\n * `config` defaults to the global singleton. Pass the SAME config you gave\n * `<AparteChat config={…}>`: the controller resolves the config governing its\n * host element, so a manager registered on the global is invisible to a chat\n * with its own — persistence silently does nothing while the optimistic UI\n * keeps working.\n */\n const init = useCallback(async (adapter: AparteStorageAdapter, config: AparteConfig = aparteGlobalConfig, options?: ConversationManagerOptions) => {\n const m = new AparteConversationManager(adapter, options);\n managerRef.current = m;\n unsubRef.current = m.subscribe((convs) => {\n setConversations([...convs]);\n setActiveId(m.activeId);\n });\n await m.init();\n setActiveId(m.activeId);\n config.setConversationManager(m);\n }, []);\n\n const assert = (): AparteConversationManager => {\n if (!managerRef.current) {\n throw new Error('[useConversationManager] Not initialised. Call init(adapter) first.');\n }\n return managerRef.current;\n };\n\n const activeConversations = useMemo(\n () => conversations.filter((c) => !c.archivedAt).sort((a, b) => b.updatedAt - a.updatedAt),\n [conversations],\n );\n const archivedConversations = useMemo(\n () => conversations.filter((c) => !!c.archivedAt).sort((a, b) => b.updatedAt - a.updatedAt),\n [conversations],\n );\n const activeConversation = useMemo(\n () => (activeId ? conversations.find((c) => c.id === activeId) ?? null : null),\n [conversations, activeId],\n );\n\n return {\n conversations,\n activeConversations,\n archivedConversations,\n activeId,\n activeConversation,\n init,\n get manager() { return managerRef.current; },\n createNew: (title) => assert().createNew(title),\n addMessage: (convId, message) => assert().addMessage(convId, message),\n updateMessages: (convId, messages) => assert().updateMessages(convId, messages),\n delete: (id) => assert().delete(id),\n archive: (id) => assert().archive(id),\n unarchive: (id) => assert().unarchive(id),\n pin: (id) => assert().pin(id),\n unpin: (id) => assert().unpin(id),\n updateTitle: (id, title) => assert().updateTitle(id, title),\n };\n}\n","import { useEffect, useRef, forwardRef, useImperativeHandle } from 'react';\nimport { applyElementProps, APARTE_DEFAULT_UI_EVENTS, type AparteUiHandle } from '@aparte/core';\n\nexport interface AparteUiProps {\n /** The custom element tag name (e.g. 'aparte-model-selector'). */\n name: string;\n /** Props to apply. Keys starting with `--` become CSS variables. */\n props?: Record<string, unknown>;\n /** Emits a forwarded custom event from the underlying Web Component. */\n onElementEvent?: (event: CustomEvent) => void;\n /**\n * Which custom events to forward through `onElementEvent`. Defaults to the\n * interactive aparté surface ({@link APARTE_DEFAULT_UI_EVENTS}); pass your own list to\n * listen to other events (e.g. `['aparte-composer-change']` for attachments).\n */\n events?: string[];\n}\n\n// The handle is core's, not this wrapper's: the same two-method contract was\n// hand-copied into all four, each docblock promising \"the same on all four\", and it\n// had already drifted (two of them returned `HTMLElement | null` / `unknown` instead\n// of the generic form). One declaration in `@aparte/core`, four re-exports.\nexport type { AparteUiHandle } from '@aparte/core';\n\n/**\n * Universal pass-through proxy: dynamically mounts any `aparte-*` Web Component so\n * you don't need a dedicated React wrapper per element. React equivalent of\n * Angular's `AparteUiComponent`.\n *\n * `name` can be any custom element, including one of yours. `aparte-model-selector`\n * below is defined by `@aparte/plugin-model-selector`, not by core — until that\n * package is imported the tag mounts empty and inert with no error, and upgrades on\n * its own once the definition arrives (which is what makes lazy-loading it work).\n *\n * A PLUGIN's event is not in the default forward list — that default is core's, and a\n * third-party plugin's author cannot add to it, so listing ours would privilege our\n * packages over theirs. Name it in `events` and it arrives; leave it out and the handler\n * silently never fires, which is what this example used to do.\n *\n * @example\n * <AparteUi\n * name=\"aparte-model-selector\"\n * props={{ placeholder: 'Ask…', '--glow-speed': '4s' }}\n * events={['aparte-model-change']}\n * onElementEvent={onEvent}\n * />\n */\nexport const AparteUi = forwardRef<AparteUiHandle, AparteUiProps>(function AparteUi(\n { name, props = {}, onElementEvent, events },\n ref,\n) {\n const hostRef = useRef<HTMLSpanElement>(null);\n const elRef = useRef<HTMLElement | null>(null);\n const cbRef = useRef(onElementEvent);\n cbRef.current = onElementEvent;\n const propsRef = useRef(props);\n propsRef.current = props;\n\n const evts = events ?? APARTE_DEFAULT_UI_EVENTS;\n const evtsKey = evts.join('|');\n\n // (Re)create the element when `name` (or the forwarded event set) changes.\n useEffect(() => {\n const host = hostRef.current;\n if (!host) return;\n const el = document.createElement(name);\n elRef.current = el;\n // The props effect below compares `props` by identity, so a memoized prop bag\n // never re-applies on a `name`/`events` change. Apply through a ref here — the\n // same shape Vue/Svelte/Angular use inside their own create().\n applyElementProps(el, propsRef.current);\n const cleanups: Array<() => void> = [];\n for (const ev of evtsKey.split('|').filter(Boolean)) {\n const listener = (e: Event) => cbRef.current?.(e as CustomEvent);\n el.addEventListener(ev, listener);\n cleanups.push(() => el.removeEventListener(ev, listener));\n }\n host.appendChild(el);\n return () => {\n for (const c of cleanups) c();\n el.remove();\n elRef.current = null;\n };\n }, [name, evtsKey]);\n\n // Apply props whenever they change.\n useEffect(() => {\n if (elRef.current) applyElementProps(elRef.current, props);\n }, [props]);\n\n useImperativeHandle(ref, (): AparteUiHandle => ({\n getElement: () => elRef.current as never,\n callMethod: (methodName, ...args) => {\n const fn = (elRef.current as unknown as Record<string, unknown>)?.[methodName];\n return typeof fn === 'function'\n ? (fn as (...a: unknown[]) => unknown).apply(elRef.current, args) as never\n : undefined;\n },\n }), []);\n\n return <span ref={hostRef} style={{ display: 'contents' }} />;\n});\n\nAparteUi.displayName = 'AparteUi';\n","/**\n * Your own React component in a tool call, or in a segment of a type of your own.\n *\n * `<AparteToolUI name=\"get_weather\">{(tool) => <WeatherCard … />}</AparteToolUI>` draws every\n * `get_weather` call with your component for as long as the declaration is mounted, and gives\n * the calls back to core's row when it unmounts. The card is a portal: it renders from where\n * the declaration sits in your tree, so it reads your contexts (a theme, a router, a store) like\n * any other component there.\n *\n * Underneath it is core's `overrideToolRenderer` + `defineMountedToolRenderer`: core hands each\n * matching call an element and every newer state of the call; this component keeps the elements\n * in a small store React reads, and portals the children into each one. Nothing registers on\n * the server, so a server render draws nothing here, and the calls are drawn in the browser.\n */\nimport React, { useEffect, useState, useSyncExternalStore } from 'react';\nimport { createPortal } from 'react-dom';\nimport {\n aparteGlobalConfig,\n defineMountedSegmentRenderer,\n defineMountedToolRenderer,\n overrideSegmentRenderer,\n type AparteConfig,\n type AparteMountedView,\n type AparteSegmentBase,\n type AparteToolCallView,\n type AparteToolMatcher,\n} from '@aparte/core';\n\nconst NONE: ReadonlyMap<HTMLElement, never> = new Map<HTMLElement, never>();\nconst serverSnapshot = () => NONE;\n\n/** The elements core handed one declaration, each with the latest state it was given. */\nclass MountedViews<V> {\n private readonly views = new Map<HTMLElement, V>();\n private snapshot: ReadonlyMap<HTMLElement, V> = NONE;\n private readonly listeners = new Set<() => void>();\n private queued = false;\n // A key per element, not the call's id: a model can issue `call_0` on every turn.\n private readonly keys = new WeakMap<HTMLElement, string>();\n private serial = 0;\n\n readonly subscribe = (listener: () => void) => {\n this.listeners.add(listener);\n return () => { this.listeners.delete(listener); };\n };\n\n readonly getSnapshot = () => this.snapshot;\n\n readonly keyOf = (host: HTMLElement) => this.keys.get(host);\n\n readonly mount = (host: HTMLElement, view: V): AparteMountedView<V> => {\n this.keys.set(host, String(++this.serial));\n this.views.set(host, view);\n this.changed();\n return {\n update: (next) => { this.views.set(host, next); this.changed(); },\n destroy: () => { if (this.views.delete(host)) this.changed(); },\n };\n };\n\n // Core mounts from its own rendering, which can run inside another component's commit\n // (the chat's effects append the bubbles). React hears about it in a microtask, once for\n // however many calls changed together.\n private changed() {\n if (this.queued) return;\n this.queued = true;\n queueMicrotask(() => {\n this.queued = false;\n this.snapshot = new Map(this.views);\n for (const listener of this.listeners) listener();\n });\n }\n}\n\n/** A matcher as an effect dependency: a `RegExp` literal is a new object on every render. */\nconst matcherKey = (match: AparteToolMatcher) => (typeof match === 'string' ? match : String(match));\n\nexport interface AparteToolUIProps<I = unknown, R = unknown> {\n /** The tool this draws: a name, a `RegExp`, or `'*'` for every tool. */\n name: AparteToolMatcher;\n /** The config to declare it on. Defaults to the global one. */\n config?: AparteConfig;\n /** What to draw for one call: its id, name, input, status, result and the rest. */\n children: (tool: AparteToolCallView<I, R>) => React.ReactNode;\n}\n\n/**\n * Draw a tool call with your own component, for as long as this is mounted.\n *\n * @example\n * <AparteToolUI<{ city: string }> name=\"get_weather\">\n * {(tool) => <WeatherCard city={tool.input.city} status={tool.status} />}\n * </AparteToolUI>\n */\nexport function AparteToolUI<I = unknown, R = unknown>({ name, config, children }: AparteToolUIProps<I, R>) {\n const [views] = useState(() => new MountedViews<AparteToolCallView>());\n const key = matcherKey(name);\n useEffect(\n () => (config ?? aparteGlobalConfig).overrideToolRenderer(name, defineMountedToolRenderer(views.mount)),\n // `name` is read through its key: the same pattern written again is the same declaration.\n [key, config, views],\n );\n const mounted = useSyncExternalStore(views.subscribe, views.getSnapshot, serverSnapshot);\n return <>{Array.from(mounted, ([host, view]) => createPortal(children(view as AparteToolCallView<I, R>), host, views.keyOf(host)))}</>;\n}\n\nexport interface AparteSegmentUIProps<T extends AparteSegmentBase> {\n /** The segment `type` this draws — one your stream emits, or a built-in such as `code`. */\n type: T['type'];\n /** The config to declare it on. Defaults to the global one. */\n config?: AparteConfig;\n /** What to draw for one segment, handed its latest state. */\n children: (segment: T) => React.ReactNode;\n}\n\n/**\n * Draw a segment type with your own component, for as long as this is mounted. Taking the\n * last declaration off a built-in type gives it back to core's renderer.\n */\nexport function AparteSegmentUI<T extends AparteSegmentBase>({ type, config, children }: AparteSegmentUIProps<T>) {\n const [views] = useState(() => new MountedViews<T>());\n useEffect(() => {\n const renderer = defineMountedSegmentRenderer<T>(type, views.mount);\n return config ? overrideSegmentRenderer(renderer, config) : overrideSegmentRenderer(renderer);\n }, [type, config, views]);\n const mounted = useSyncExternalStore(views.subscribe, views.getSnapshot, serverSnapshot);\n return <>{Array.from(mounted, ([host, segment]) => createPortal(children(segment), host, views.keyOf(host)))}</>;\n}\n"],"names":["AparteChat","composer","AparteUi"],"mappings":";;;;;AAMO,SAAS,iBAAiB,IAAsB;AACnD,MAAI,OAAO,aAAa,eAAe,SAAS,oBAAoB,SAAU,YAAW,IAAI,CAAC;AAAA,MACzF,uBAAsB,MAAM,IAAI;AACzC;AC0LA,MAAM,cAA+B,CAAA;AAGrC,MAAM,qBAAqB,MAAM,MAAM;AAAC;AACxC,MAAM,cAAc,MAAM;AAC1B,MAAM,iBAAiB,MAAM;AAEtB,MAAM,aAAa,WAAqD,SAASA,YACpF;AAAA,EACI,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,WAAW;AAAA,EACX;AAAA,EACA,gBAAgB;AAAA,EAChB,qBAAqB;AAAA,EACrB,kBAAkB;AAAA,EAClB,UAAU;AAAA,EACV,kBAAkB;AAAA,EAClB,cAAc;AAAA,EACd,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,IAAI;AACR,GACA,KACF;AAIE,QAAM,cAAc,eAAe,MAAA,EAAQ,QAAQ,MAAM,EAAE,CAAC;AAC5D,QAAM,SAAS,cAAc;AAC7B,QAAM,YAAY,OAAuB,IAAI;AAI7C,QAAM,YAAY,qBAAqB,oBAAoB,gBAAgB,WAAW;AACtF,QAAM,cAAc,OAAoB,IAAI;AAC5C,QAAM,cAAc,OAAoB,IAAI;AAI5C,QAAM,cAAc,OAAwB,QAAQ;AACpD,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAA0B,QAAQ;AAC9E,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,QAAQ;AAEzD,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,KAAK;AACpD,QAAM,UAAU,WAAW;AAG3B,QAAM,cAAc,UAAU;AAC9B,QAAM,aAAa,MAAc,GAAG,YAAY,EAAE,qBAAqB,CAAC,KAAS,YAAY,EAAE,QAAQ,CAAC;AACxG,QAAM,SAAS,qBAAqB,YAAY,CAAC,WAAW,YAAY,UAAU,MAAM,GAAG,CAAC,WAAW,CAAC,GAAG,YAAY,UAAU;AACjI,QAAM,CAAC,cAAc,IAAI,eAAe,EAAE,IAAI,OAAO,MAAM,IAAQ;AAEnE,QAAM,cAAc,cAAc;AAClC,QAAM,GAAG,cAAc,IAAI,SAAS,KAAK;AAEzC,QAAM,UAAU,OAA8B,IAAI;AAClD,QAAM,cAAc,OAAsB,cAAc;AAIxD,QAAM,QAAQ,OAAO,EAAE,eAAe,UAAU,kBAAkB,mBAAmB,gBAAgB,uBAAuB;AAC5H,QAAM,UAAU,EAAE,eAAe,UAAU,kBAAkB,mBAAmB,gBAAgB,sBAAA;AAEhG,QAAM,gBAAgB,CAAC,MAAuB;AAC1C,gBAAY,UAAU;AACtB,sBAAkB,CAAC;AAAA,EACvB;AAIA,YAAU,MAAM;AACZ,UAAM,OAAO,UAAU;AACvB,QAAI,CAAC,KAAM;AACX,6BAAyB,IAAI;AAC7B,UAAM,UAAiC;AAAA,MACnC;AAAA,MACA;AAAA,MACA,UAAU,YAAY;AAAA,MACtB,aAAa,MAAM,YAAY;AAAA,MAC/B,aAAa,CAAC,MAAM,cAAc,CAAoB;AAAA,MACtD,kBAAkB,CAAC,MAAM,MAAM,QAAQ,mBAAmB,CAAoB;AAAA,MAC9E,mBAAmB,CAAC,MAAM,MAAM,QAAQ,oBAAoB,CAAkB;AAAA,MAC9E,gBAAgB,CAAC,MAAM;AAAE,wBAAgB,CAAC;AAAG,cAAM,QAAQ,iBAAiB,CAAC;AAAA,MAAG;AAAA,MAChF,mBAAmB,CAAC,OAAO,eAAe,OAAO,IAAI;AAAA,MACrD,aAAa;AAAA,MACb,eAAe,MAAO,YAAY,SAA+C,QAAA;AAAA,MACjF,iBAAiB,CAAC,OAAO,eAAe,EAAE;AAAA,IAAA;AAE9C,UAAM,IAAI,IAAI,eAAe,SAAS;AAAA,MAClC;AAAA,MACA,gBAAgB,kBAAkB;AAAA,MAClC,uBAAuB,CAAC,OAAO,MAAM,QAAQ,wBAAwB,EAAE;AAAA,MACvE;AAAA,IAAA,CACH;AACD,YAAQ,UAAU;AAClB,UAAM,WAAW,EAAE,KAAA;AACnB,WAAO,MAAM;AAAE,eAAA;AAAY,cAAQ,UAAU;AAAA,IAAM;AAAA,EAGvD,GAAG,CAAC,MAAM,CAAC;AASX,YAAU,MAAM;AACZ,UAAM,OAAO,QAAQ,SAAS,SAAS,QAAQ,KAAK;AACpD,QAAI,QAAQ,aAAa,YAAY,QAAS;AAC9C,kBAAc,QAAQ;AACtB,QAAI,SAAS,WAAW,EAAG,SAAQ,SAAS,iBAAA;AAAA,EAChD,GAAG,CAAC,QAAQ,CAAC;AAIb,YAAU,MAAM;AAAE,YAAQ,SAAS,YAAA;AAAA,EAAe,GAAG,CAAC,cAAc,CAAC;AAIrE,YAAU,MAAM;AAAE,oBAAgB,QAAQ;AAAA,EAAG,GAAG,CAAC,QAAQ,CAAC;AAG1D,YAAU,MAAM;AACZ,QAAI,mBAAmB,YAAY,QAAS;AAC5C,gBAAY,UAAU;AACtB,SAAK,QAAQ,SAAS,kBAAkB,kBAAkB,IAAI;AAAA,EAClE,GAAG,CAAC,cAAc,CAAC;AAInB,YAAU,MAAM;AACZ,UAAMC,YAAW,YAAY;AAC7B,QAAI,CAACA,UAAU;AACf,UAAM,SAAS,CAAC,MAAa;AACxB,kBAAY,SAA6D,sBAAA;AAC1E,YAAM,QAAQ,gBAAiB,EAAyC,MAAM;AAAA,IAClF;AACAA,cAAS,iBAAiB,eAAe,MAAM;AAC/C,WAAO,MAAMA,UAAS,oBAAoB,eAAe,MAAM;AAAA,EACnE,GAAG,CAAA,CAAE;AAQL,YAAU,MAAM;AACZ,UAAMA,YAAW,YAAY;AAC7B,QAAI,CAACA,UAAU;AACfA,cAAS,aAAa,eAAe,WAAW;AAChD,QAAI,YAAY,QAASA,WAAS,aAAa,YAAY,EAAE;AAAA,QACxDA,WAAS,gBAAgB,UAAU;AAAA,EAC5C,GAAG,CAAC,aAAa,UAAU,OAAO,CAAC;AAGnC,YAAU,MAAM;AACZ,gBAAY,SAAS,gBAAgB,WAAW,OAAO;AAAA,EAC3D,GAAG,CAAC,OAAO,CAAC;AAIZ,YAAU,MAAM;AACZ,UAAM,OAAO,UAAU;AACvB,QAAI,CAAC,KAAM;AACX,UAAM,QAAQ,CAAC,MAAa,MAAM,QAAQ,WAAY,EAA2C,MAAM;AACvG,SAAK,iBAAiB,iBAAiB,KAAK;AAC5C,WAAO,MAAM,KAAK,oBAAoB,iBAAiB,KAAK;AAAA,EAChE,GAAG,CAAA,CAAE;AAEL,sBAAoB,KAAK,OAAgC;AAAA,IACrD,eAAe,CAAC,GAAG,MAAM,QAAQ,SAAS,cAAc,GAAG,CAAC;AAAA,IAC5D,eAAe,CAAC,IAAI,MAAM,QAAQ,SAAS,cAAc,IAAI,CAAC;AAAA,IAC9D,mBAAmB,CAAC,GAAG,MAAM,QAAQ,SAAS,kBAAkB,GAAG,CAAC;AAAA,IACpE,YAAY,CAAC,MAAM,QAAQ,SAAS,WAAW,CAAC;AAAA,IAChD,eAAe,CAAC,IAAI,MAAM,QAAQ,SAAS,cAAc,IAAI,CAAC;AAAA,IAC9D,eAAe,CAAC,OAAO,QAAQ,SAAS,cAAc,EAAE;AAAA,IACxD,iBAAiB,CAAC,IAAI,MAAM,QAAQ,SAAS,gBAAgB,IAAI,CAAC;AAAA,IAClE,aAAa,MAAM,QAAQ,SAAS,YAAA,KAAiB,YAAY;AAAA,IACjE,eAAe,CAAC,MAAM,QAAQ,SAAS,cAAc,CAAC;AAAA,IACtD,WAAW,CAAC,OAAO,QAAQ,SAAS,UAAU,EAAE,KAAK;AAAA,IACrD,cAAc,CAAC,IAAI,MAAM,QAAQ,SAAS,aAAa,IAAI,CAAC,KAAK;AAAA,IACjE,cAAc,CAAC,OAAO,QAAQ,SAAS,aAAa,EAAE;AAAA,IACtD,wBAAwB,CAAC,OAAO,QAAQ,SAAS,uBAAuB,EAAE;AAAA,IAC1E,mBAAmB,CAAC,IAAI,WAAW,QAAQ,SAAS,aAAa,IAAI,MAAM,KAAK,QAAQ,QAAA;AAAA,IACxF,iBAAiB,MAAM,QAAQ,SAAS,gBAAA;AAAA,IACxC,mBAAmB,CAAC,OAAO,QAAQ,SAAS,kBAAkB,EAAE,KAAK,QAAQ,QAAA;AAAA,IAC7E,gBAAgB,MAAO,YAAY,SAAwD,iBAAA;AAAA,IAC3F,YAAY,MAAO,YAAY,SAA+C,QAAA;AAAA,IAC9E,aAAa,MAAM,QAAQ,SAAS,eAAe;AAAA,IACnD,aAAa,MAAM,YAAY;AAAA,EAAA,IAC/B,CAAA,CAAE;AAEN,SACI;AAAA,IAAC;AAAA,IAAA;AAAA,MACG,WAAW,wBAAwB,kBAAkB,wCAAwC,EAAE,GAAG,YAAY,IAAI,SAAS,KAAK,EAAE;AAAA,MAClI;AAAA,MACA,oBAAiB;AAAA,MAChB,GAAI,YAAY,EAAE,mBAAmB,GAAA,IAAO,CAAA;AAAA,MAC5C,GAAI,kBAAkB,EAAE,oBAAoB,GAAA,IAAO,CAAA;AAAA,MACpD,qBAAmB,mBAAmB,eAAe,WAAW,KAAK,CAAC,UAAU,KAAK;AAAA,MACrF,IAAI;AAAA,MACJ,KAAK;AAAA,MAEL,UAAA;AAAA,QAAA,qBAAC,wBAAA,EAAqB,KAAK,aAAuC,qBAAkB,IAE/E,UAAA;AAAA,UAAA,WACG,qBAAC,OAAA,EAAI,WAAU,2BAA0B,eAAY,QACjD,UAAA;AAAA,YAAA,oBAAC,QAAA,EAAK,WAAU,sEAAA,CAAsE;AAAA,YACtF,oBAAC,QAAA,EAAK,WAAU,wCAAA,CAAwC;AAAA,YACxD,oBAAC,QAAA,EAAK,WAAU,wCAAA,CAAwC;AAAA,YACxD,oBAAC,QAAA,EAAK,WAAU,wCAAA,CAAwC;AAAA,YACxD,oBAAC,QAAA,EAAK,WAAU,sEAAA,CAAsE;AAAA,UAAA,GAC1F;AAAA,UAEH,WAAW,oBAAC,QAAA,EAAK,WAAU,iDAAgD,MAAK,UAAU,UAAA,aAAY;AAAA,UACtG,eAAe,WAAW,KAAK,CAAC,WAAW;AAAA,UAC3C,eAAe,IAAI,CAAC,MACjB,eACM,oBAAC,MAAM,UAAN,EAA2B,UAAA,aAAa,CAAC,EAAA,GAArB,EAAE,EAAqB,IAE1C;AAAA,YAAC;AAAA,YAAA;AAAA,cAEG,cAAY,EAAE;AAAA,cACd,aAAW,EAAE;AAAA,cACb,aAAW,EAAE,aAAa,eAAe;AAAA,cACzC,WAAW,EAAE;AAAA,cACb,SAAS,EAAE;AAAA,cACX,WAAW,gBAAgB,CAAC,IAAI,KAAK;AAAA,YAAA;AAAA,YANhC,EAAE;AAAA,UAAA,CAStB;AAAA,8BACA,sBAAA,EAAmB,SAAS,eAAe,KAAK,QAAW,MAAM,YAAA,CAAa;AAAA,QAAA,GACnF;AAAA,QAEC,mCAAgB,sBAAA,EAAmB;AAAA,QAEnC;AAAA,QAED;AAAA,UAAC;AAAA,UAAA;AAAA,YACG,KAAK;AAAA,YACL,QAAQ;AAAA,YACR,mBAAiB,gBAAgB,SAAY;AAAA,YAE5C,UAAA,YACG,qBAAC,OAAA,EAAI,WAAU,yBACV,UAAA;AAAA,cAAA,mCAAgB,+BAAA,EAA4B;AAAA,cAC7C,qBAAC,OAAA,EAAI,WAAU,uBACV,UAAA;AAAA,gBAAA,mCAAgB,kCAAA,EAA+B;AAAA,oCAC/C,yBAAA,EAAsB;AAAA,oCACtB,wBAAA,CAAA,CAAqB;AAAA,cAAA,GAC1B;AAAA,cACC,WAAW,QACR,oBAAC,2BAAA,EAAyB,UAAA,QAAA,CAAQ;AAAA,YAAA,EAAA,CAE1C;AAAA,UAAA;AAAA,QAAA;AAAA,MAER;AAAA,IAAA;AAAA,EAAA;AAGZ,CAAC;AAED,WAAW,cAAc;AClalB,SAAS,cAAc,UAA2B,IAAmB;AACxE,QAAM,CAAC,UAAU,WAAW,IAAI,SAA0B,OAAO;AACjE,QAAM,MAAM,OAAuC,IAAI;AACvD,QAAM,IAAI,MAAM,IAAI;AACpB,SAAO;AAAA,IACH;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe,CAAC,GAAG,MAAM,KAAK,cAAc,GAAG,CAAC;AAAA,IAChD,eAAe,CAAC,IAAI,MAAM,KAAK,cAAc,IAAI,CAAC;AAAA,IAClD,mBAAmB,CAAC,GAAG,MAAM,KAAK,kBAAkB,GAAG,CAAC;AAAA,IACxD,YAAY,CAAC,MAAM,EAAA,GAAK,WAAW,CAAC;AAAA,IACpC,eAAe,CAAC,IAAI,MAAM,KAAK,cAAc,IAAI,CAAC;AAAA,IAClD,eAAe,CAAC,OAAO,EAAA,GAAK,cAAc,EAAE;AAAA,IAC5C,iBAAiB,CAAC,IAAI,MAAM,KAAK,gBAAgB,IAAI,CAAC;AAAA,IACtD,aAAa,MAAM,KAAK,iBAAiB;AAAA,IACzC,eAAe,CAAC,MAAM,EAAA,GAAK,cAAc,CAAC;AAAA,IAC1C,WAAW,CAAC,OAAO,KAAK,UAAU,EAAE,KAAK;AAAA,IACzC,cAAc,CAAC,IAAI,MAAM,KAAK,aAAa,IAAI,CAAC,KAAK;AAAA,IACrD,cAAc,CAAC,OAAO,EAAA,GAAK,aAAa,EAAE;AAAA,IAC1C,wBAAwB,CAAC,OAAO,EAAA,GAAK,uBAAuB,EAAE;AAAA,IAC9D,mBAAmB,CAAC,IAAI,WAAW,EAAA,GAAK,kBAAkB,IAAI,MAAM,KAAK,QAAQ,QAAA;AAAA,IACjF,iBAAiB,MAAM,EAAA,GAAK,gBAAA;AAAA,IAC5B,mBAAmB,CAAC,OAAO,EAAA,GAAK,kBAAkB,EAAE,KAAK,QAAQ,QAAA;AAAA,IACjE,aAAa,MAAM,KAAK,iBAAiB;AAAA,IACzC,gBAAgB,MAAM,EAAA,GAAK,eAAA;AAAA,IAC3B,YAAY,MAAM,EAAA,GAAK,WAAA;AAAA,IACvB,aAAa,MAAM,KAAK,iBAAiB;AAAA,EAAA;AAEjD;AC9DO,SAAS,gBAAgB,SAAgD;AAC5E,QAAM,MAAM,OAA4B,IAAI;AAC5C,MAAI,CAAC,IAAI,QAAS,KAAI,UAAU,IAAI,aAAa,WAAW,EAAE;AAC9D,YAAU,MAAM;AACZ,UAAM,IAAI,IAAI;AACd,MAAE,MAAA;AACF,WAAO,MAAM,EAAE,KAAA;AAAA,EACnB,GAAG,CAAA,CAAE;AACL,SAAO,EAAE,QAAQ,IAAI,SAAS,OAAO,MAAM,IAAI,SAAS,QAAM;AAClE;ACcO,SAAS,yBAAiD;AAC7D,QAAM,aAAa,OAAyC,IAAI;AAChE,QAAM,WAAW,OAA4B,IAAI;AACjD,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAA+B,CAAA,CAAE;AAC3E,QAAM,CAAC,UAAU,WAAW,IAAI,SAAwB,IAAI;AAE5D,YAAU,MAAM,MAAM,SAAS,UAAA,GAAa,CAAA,CAAE;AAW9C,QAAM,OAAO,YAAY,OAAO,SAA+B,SAAuB,oBAAoB,YAAyC;AAC/I,UAAM,IAAI,IAAI,0BAA0B,SAAS,OAAO;AACxD,eAAW,UAAU;AACrB,aAAS,UAAU,EAAE,UAAU,CAAC,UAAU;AACtC,uBAAiB,CAAC,GAAG,KAAK,CAAC;AAC3B,kBAAY,EAAE,QAAQ;AAAA,IAC1B,CAAC;AACD,UAAM,EAAE,KAAA;AACR,gBAAY,EAAE,QAAQ;AACtB,WAAO,uBAAuB,CAAC;AAAA,EACnC,GAAG,CAAA,CAAE;AAEL,QAAM,SAAS,MAAiC;AAC5C,QAAI,CAAC,WAAW,SAAS;AACrB,YAAM,IAAI,MAAM,qEAAqE;AAAA,IACzF;AACA,WAAO,WAAW;AAAA,EACtB;AAEA,QAAM,sBAAsB;AAAA,IACxB,MAAM,cAAc,OAAO,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;AAAA,IACzF,CAAC,aAAa;AAAA,EAAA;AAElB,QAAM,wBAAwB;AAAA,IAC1B,MAAM,cAAc,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;AAAA,IAC1F,CAAC,aAAa;AAAA,EAAA;AAElB,QAAM,qBAAqB;AAAA,IACvB,MAAO,WAAW,cAAc,KAAK,CAAC,MAAM,EAAE,OAAO,QAAQ,KAAK,OAAO;AAAA,IACzE,CAAC,eAAe,QAAQ;AAAA,EAAA;AAG5B,SAAO;AAAA,IACH;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,IAAI,UAAU;AAAE,aAAO,WAAW;AAAA,IAAS;AAAA,IAC3C,WAAW,CAAC,UAAU,OAAA,EAAS,UAAU,KAAK;AAAA,IAC9C,YAAY,CAAC,QAAQ,YAAY,SAAS,WAAW,QAAQ,OAAO;AAAA,IACpE,gBAAgB,CAAC,QAAQ,aAAa,SAAS,eAAe,QAAQ,QAAQ;AAAA,IAC9E,QAAQ,CAAC,OAAO,OAAA,EAAS,OAAO,EAAE;AAAA,IAClC,SAAS,CAAC,OAAO,OAAA,EAAS,QAAQ,EAAE;AAAA,IACpC,WAAW,CAAC,OAAO,OAAA,EAAS,UAAU,EAAE;AAAA,IACxC,KAAK,CAAC,OAAO,OAAA,EAAS,IAAI,EAAE;AAAA,IAC5B,OAAO,CAAC,OAAO,OAAA,EAAS,MAAM,EAAE;AAAA,IAChC,aAAa,CAAC,IAAI,UAAU,SAAS,YAAY,IAAI,KAAK;AAAA,EAAA;AAElE;ACnEO,MAAM,WAAW,WAA0C,SAASC,UACvE,EAAE,MAAM,QAAQ,CAAA,GAAI,gBAAgB,OAAA,GACpC,KACF;AACE,QAAM,UAAU,OAAwB,IAAI;AAC5C,QAAM,QAAQ,OAA2B,IAAI;AAC7C,QAAM,QAAQ,OAAO,cAAc;AACnC,QAAM,UAAU;AAChB,QAAM,WAAW,OAAO,KAAK;AAC7B,WAAS,UAAU;AAEnB,QAAM,OAAO,UAAU;AACvB,QAAM,UAAU,KAAK,KAAK,GAAG;AAG7B,YAAU,MAAM;AACZ,UAAM,OAAO,QAAQ;AACrB,QAAI,CAAC,KAAM;AACX,UAAM,KAAK,SAAS,cAAc,IAAI;AACtC,UAAM,UAAU;AAIhB,sBAAkB,IAAI,SAAS,OAAO;AACtC,UAAM,WAA8B,CAAA;AACpC,eAAW,MAAM,QAAQ,MAAM,GAAG,EAAE,OAAO,OAAO,GAAG;AACjD,YAAM,WAAW,CAAC,MAAa,MAAM,UAAU,CAAgB;AAC/D,SAAG,iBAAiB,IAAI,QAAQ;AAChC,eAAS,KAAK,MAAM,GAAG,oBAAoB,IAAI,QAAQ,CAAC;AAAA,IAC5D;AACA,SAAK,YAAY,EAAE;AACnB,WAAO,MAAM;AACT,iBAAW,KAAK,SAAU,GAAA;AAC1B,SAAG,OAAA;AACH,YAAM,UAAU;AAAA,IACpB;AAAA,EACJ,GAAG,CAAC,MAAM,OAAO,CAAC;AAGlB,YAAU,MAAM;AACZ,QAAI,MAAM,QAAS,mBAAkB,MAAM,SAAS,KAAK;AAAA,EAC7D,GAAG,CAAC,KAAK,CAAC;AAEV,sBAAoB,KAAK,OAAuB;AAAA,IAC5C,YAAY,MAAM,MAAM;AAAA,IACxB,YAAY,CAAC,eAAe,SAAS;AACjC,YAAM,KAAM,MAAM,UAAiD,UAAU;AAC7E,aAAO,OAAO,OAAO,aACd,GAAoC,MAAM,MAAM,SAAS,IAAI,IAC9D;AAAA,IACV;AAAA,EAAA,IACA,CAAA,CAAE;AAEN,SAAO,oBAAC,UAAK,KAAK,SAAS,OAAO,EAAE,SAAS,cAAc;AAC/D,CAAC;AAED,SAAS,cAAc;AC3EvB,MAAM,2BAA4C,IAAA;AAClD,MAAM,iBAAiB,MAAM;AAG7B,MAAM,aAAgB;AAAA,EACD,4BAAY,IAAA;AAAA,EACrB,WAAwC;AAAA,EAC/B,gCAAgB,IAAA;AAAA,EACzB,SAAS;AAAA;AAAA,EAEA,2BAAW,QAAA;AAAA,EACpB,SAAS;AAAA,EAER,YAAY,CAAC,aAAyB;AAC3C,SAAK,UAAU,IAAI,QAAQ;AAC3B,WAAO,MAAM;AAAE,WAAK,UAAU,OAAO,QAAQ;AAAA,IAAG;AAAA,EACpD;AAAA,EAES,cAAc,MAAM,KAAK;AAAA,EAEzB,QAAQ,CAAC,SAAsB,KAAK,KAAK,IAAI,IAAI;AAAA,EAEjD,QAAQ,CAAC,MAAmB,SAAkC;AACnE,SAAK,KAAK,IAAI,MAAM,OAAO,EAAE,KAAK,MAAM,CAAC;AACzC,SAAK,MAAM,IAAI,MAAM,IAAI;AACzB,SAAK,QAAA;AACL,WAAO;AAAA,MACH,QAAQ,CAAC,SAAS;AAAE,aAAK,MAAM,IAAI,MAAM,IAAI;AAAG,aAAK,QAAA;AAAA,MAAW;AAAA,MAChE,SAAS,MAAM;AAAE,YAAI,KAAK,MAAM,OAAO,IAAI,QAAQ,QAAA;AAAA,MAAW;AAAA,IAAA;AAAA,EAEtE;AAAA;AAAA;AAAA;AAAA,EAKQ,UAAU;AACd,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AACd,mBAAe,MAAM;AACjB,WAAK,SAAS;AACd,WAAK,WAAW,IAAI,IAAI,KAAK,KAAK;AAClC,iBAAW,YAAY,KAAK,UAAW,UAAA;AAAA,IAC3C,CAAC;AAAA,EACL;AACJ;AAGA,MAAM,aAAa,CAAC,UAA8B,OAAO,UAAU,WAAW,QAAQ,OAAO,KAAK;AAmB3F,SAAS,aAAuC,EAAE,MAAM,QAAQ,YAAqC;AACxG,QAAM,CAAC,KAAK,IAAI,SAAS,MAAM,IAAI,cAAkC;AACrE,QAAM,MAAM,WAAW,IAAI;AAC3B;AAAA,IACI,OAAO,UAAU,oBAAoB,qBAAqB,MAAM,0BAA0B,MAAM,KAAK,CAAC;AAAA;AAAA,IAEtG,CAAC,KAAK,QAAQ,KAAK;AAAA,EAAA;AAEvB,QAAM,UAAU,qBAAqB,MAAM,WAAW,MAAM,aAAa,cAAc;AACvF,yCAAU,UAAA,MAAM,KAAK,SAAS,CAAC,CAAC,MAAM,IAAI,MAAM,aAAa,SAAS,IAAgC,GAAG,MAAM,MAAM,MAAM,IAAI,CAAC,CAAC,GAAE;AACvI;AAeO,SAAS,gBAA6C,EAAE,MAAM,QAAQ,YAAqC;AAC9G,QAAM,CAAC,KAAK,IAAI,SAAS,MAAM,IAAI,cAAiB;AACpD,YAAU,MAAM;AACZ,UAAM,WAAW,6BAAgC,MAAM,MAAM,KAAK;AAClE,WAAO,SAAS,wBAAwB,UAAU,MAAM,IAAI,wBAAwB,QAAQ;AAAA,EAChG,GAAG,CAAC,MAAM,QAAQ,KAAK,CAAC;AACxB,QAAM,UAAU,qBAAqB,MAAM,WAAW,MAAM,aAAa,cAAc;AACvF,yCAAU,UAAA,MAAM,KAAK,SAAS,CAAC,CAAC,MAAM,OAAO,MAAM,aAAa,SAAS,OAAO,GAAG,MAAM,MAAM,MAAM,IAAI,CAAC,CAAC,GAAE;AACjH;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aparte/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"homepage": "https://apartejs.dev/frameworks/react/",
|
|
5
5
|
"description": "React 18+ wrapper for aparté — an ergonomic <AparteChat> component plus hooks over the framework-agnostic web components.",
|
|
6
6
|
"type": "module",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"node": ">=18"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"@aparte/core": ">=0.
|
|
28
|
+
"@aparte/core": ">=0.25.0 <1.0.0",
|
|
29
29
|
"react": "^18.0.0 || ^19.0.0",
|
|
30
30
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
31
31
|
},
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"react-dom": "^19.2.7",
|
|
41
41
|
"typescript": "^5.4.0",
|
|
42
42
|
"vite": "^6.0.0",
|
|
43
|
-
"@aparte/
|
|
43
|
+
"@aparte/provider-scenario": "0.25.0",
|
|
44
|
+
"@aparte/core": "0.25.0"
|
|
44
45
|
},
|
|
45
46
|
"keywords": [
|
|
46
47
|
"react",
|