@aparte/react 0.2.0-alpha.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 aparté
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # @aparte/react
2
+
3
+ React 18/19 wrapper for [aparté](https://github.com/apartejs/aparte) — an ergonomic `<AparteChat>`
4
+ component plus hooks (`useAparteChat`, `useAparteClient`, `useConversationManager`) over the
5
+ framework-agnostic web components in `@aparte/core`.
6
+
7
+ ```bash
8
+ npm install @aparte/react @aparte/core react react-dom
9
+ ```
10
+
11
+ ```tsx
12
+ import { AparteChat, useAparteChat } from '@aparte/react';
13
+ import '@aparte/core/styles.css';
14
+
15
+ function Chat() {
16
+ const chat = useAparteChat();
17
+ return (
18
+ <AparteChat
19
+ ref={chat.ref}
20
+ messages={chat.messages}
21
+ onMessagesChange={chat.setMessages}
22
+ />
23
+ );
24
+ }
25
+ ```
26
+
27
+ The user's message is appended automatically on send — don't add it yourself. `onMessageSent` is
28
+ optional and only for side-effects (scroll, analytics).
29
+
30
+ `@aparte/core`, `react` and `react-dom` are **peer dependencies**. For any `<aparte-*>` element
31
+ without a dedicated component, the generic `<AparteUi name="aparte-…" />` escape hatch mounts it.
32
+
33
+ > ESM-only. See the docs for the full API. Part of the aparté monorepo.
@@ -0,0 +1,110 @@
1
+ import React from 'react';
2
+ import { type AparteConfigClass, type AparteChatImperativeApi } from '@aparte/core';
3
+ import type { AparteMessage, AparteSendEventDetail, AparteActionEventDetail } from '../types.js';
4
+ export interface AparteChatProps {
5
+ /**
6
+ * Messages on the active path. **Optional** — omit for an uncontrolled chat
7
+ * that starts empty (defaults to `[]`); pass it together with
8
+ * `onMessagesChange` to control the list from the parent.
9
+ */
10
+ messages?: AparteMessage[];
11
+ placeholder?: string;
12
+ disabled?: boolean;
13
+ isTyping?: boolean;
14
+ typingText?: string;
15
+ /** When false, Shift+Enter submits and a bare Enter inserts a newline. */
16
+ submitOnEnter?: boolean;
17
+ /** Freeze viewport spacer recalculation for this many ms after a conv swap. */
18
+ layoutTransitionMs?: number;
19
+ /**
20
+ * Opt in to the "centered composer when empty" layout: while the message
21
+ * list is empty the composer sits vertically centered with the `emptyState`
22
+ * content above it, then slides to the bottom on the first message (~0.3s).
23
+ * Off by default — purely additive: adds the `aparte-chat-container--auto-center`
24
+ * modifier + a `data-aparte-empty` attribute that the shipped `aparte.css` recipe
25
+ * keys off. No effect unless you also render an `emptyState`.
26
+ */
27
+ centerWhenEmpty?: boolean;
28
+ /**
29
+ * Active conversation id. When set, the wrapper loads/persists via the
30
+ * `ConversationManager` registered in `AparteConfig` (set `null` to deselect).
31
+ */
32
+ conversationId?: string | null;
33
+ /**
34
+ * Custom composer content, rendered inside `<aparte-composer>` in place of the
35
+ * default shell (add-attachment · input · send). Compose the headless
36
+ * `aparte-composer-*` primitives freely — e.g. a skin-specific layout. Omit for
37
+ * the default shell. The `<aparte-composer>` element (and its placeholder /
38
+ * disabled / submit-on-enter behaviour) is always provided by the wrapper.
39
+ */
40
+ composer?: React.ReactNode;
41
+ /**
42
+ * Render your OWN element per message in place of `<aparte-chat-bubble>`.
43
+ * Opt-in — omit for the default bubble. The returned node is driven by the
44
+ * reactive message list, so it updates live during streaming (no need to
45
+ * implement any imperative interface): re-render from `message.content` /
46
+ * `message.segments`. Note the built-in action bar (retry/edit/branch) and
47
+ * the imperative streaming push are the native bubble's — a custom bubble
48
+ * owns whatever it wires (it can dispatch `aparte-retry` etc. or call the
49
+ * wrapper's imperative API).
50
+ */
51
+ renderBubble?: (message: AparteMessage) => React.ReactNode;
52
+ /**
53
+ * Welcome / placeholder content shown INSIDE the viewport while there are no
54
+ * messages (a real "empty state" region, not a workaround via `aboveComposer`).
55
+ * Replaced by the message list on the first message.
56
+ */
57
+ emptyState?: React.ReactNode;
58
+ /**
59
+ * Content rendered ABOVE the composer (e.g. a disclaimer banner, a
60
+ * "scroll to bottom" affordance, a context chip). Ignored when a full
61
+ * custom `composer` replaces the shell.
62
+ */
63
+ aboveComposer?: React.ReactNode;
64
+ /** Footer content, left slot — rendered in the default shell's footer row. */
65
+ footerLeft?: React.ReactNode;
66
+ /** Footer content, center slot. */
67
+ footerCenter?: React.ReactNode;
68
+ /** Footer content, right slot (e.g. a model selector or token counter). */
69
+ footerRight?: React.ReactNode;
70
+ /**
71
+ * Notification that the user submitted a message from the composer. The
72
+ * user's message is **appended to the thread automatically** (optimistic UI)
73
+ * before this fires — do NOT add it again here. In an uncontrolled chat,
74
+ * appending it duplicates it; in a controlled chat, mirror it into your own
75
+ * `messages`. Use this for side-effects: scroll, analytics, backend send.
76
+ */
77
+ onMessageSent?: (event: AparteSendEventDetail) => void;
78
+ /**
79
+ * Fired when a custom bubble action (registered via
80
+ * `AparteConfig.registerAction` with `zones: ['bubble']`) is clicked — a typed
81
+ * wrapper over the bubbling `aparte-action` DOM event. Dispatch on `detail.actionId`.
82
+ */
83
+ onAction?: (detail: AparteActionEventDetail) => void;
84
+ /**
85
+ * Fired when the active message path changes (branch navigation / edit /
86
+ * retry / streaming). Set the result back as the `messages` prop.
87
+ */
88
+ onMessagesChange?: (messages: AparteMessage[]) => void;
89
+ /** Fired when a message is appended internally (e.g. by AparteClient). */
90
+ onMessageAppended?: (message: AparteMessage) => void;
91
+ /** Fired when the typing/"thinking" indicator should toggle. */
92
+ onTypingChange?: (isTyping: boolean) => void;
93
+ /** Fired when the controller lazily creates a conversation on first send. */
94
+ onConversationCreated?: (id: string) => void;
95
+ /**
96
+ * Instance {@link AparteConfigClass} for this chat. When set, every aparté
97
+ * component rendered inside resolves THIS config instead of the global
98
+ * `AparteConfig` singleton — letting several independently-configured chats
99
+ * (different providers, tools, renderers) coexist on one page. Omit for the
100
+ * global config. Read once when the host mounts.
101
+ */
102
+ config?: AparteConfigClass;
103
+ }
104
+ /**
105
+ * The React ref handle for `<AparteChat>` — the canonical imperative surface
106
+ * shared by all four wrappers (see `AparteChatImperativeApi` in `@aparte/core`).
107
+ */
108
+ export type AparteChatHandle = AparteChatImperativeApi;
109
+ export declare const AparteChat: React.ForwardRefExoticComponent<AparteChatProps & React.RefAttributes<AparteChatImperativeApi>>;
110
+ //# sourceMappingURL=AparteChat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AparteChat.d.ts","sourceRoot":"","sources":["../../src/components/AparteChat.tsx"],"names":[],"mappings":"AAEA,OAAO,KAON,MAAM,OAAO,CAAC;AACf,OAAO,EAA8C,KAAK,iBAAiB,EAAE,KAAK,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAChI,OAAO,KAAK,EAAE,aAAa,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAEjG,MAAM,WAAW,eAAe;IAC5B;;;;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;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;;;;OAMG;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,8EAA8E;IAC9E,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B,mCAAmC;IACnC,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC/B,2EAA2E;IAC3E,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAE9B;;;;;;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,iBAAiB,CAAC;CAC9B;AAED;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,uBAAuB,CAAC;AAEvD,eAAO,MAAM,UAAU,iGA4NrB,CAAC"}
@@ -0,0 +1,28 @@
1
+ export interface AparteUiProps {
2
+ /** The custom element tag name (e.g. 'aparte-model-selector'). */
3
+ name: string;
4
+ /** Props to apply. Keys starting with `--` become CSS variables. */
5
+ props?: Record<string, unknown>;
6
+ /** Emits a forwarded custom event from the underlying Web Component. */
7
+ onElementEvent?: (event: CustomEvent) => void;
8
+ /**
9
+ * Which custom events to forward through `onElementEvent`. Defaults to the
10
+ * interactive aparté surface ({@link DEFAULT_UI_EVENTS}); pass your own list to
11
+ * listen to other events (e.g. `['aparte-composer-change']` for attachments).
12
+ */
13
+ events?: string[];
14
+ }
15
+ export interface AparteUiHandle {
16
+ getElement: <T extends HTMLElement = HTMLElement>() => T | null;
17
+ callMethod: <T = unknown>(methodName: string, ...args: unknown[]) => T | undefined;
18
+ }
19
+ /**
20
+ * Universal pass-through proxy: dynamically mounts any `aparte-*` Web Component so
21
+ * you don't need a dedicated React wrapper per element. React equivalent of
22
+ * Angular's `AparteUiComponent`.
23
+ *
24
+ * @example
25
+ * <AparteUi name="aparte-model-selector" props={{ placeholder: 'Ask…', '--glow-speed': '4s' }} onElementEvent={onEvent} />
26
+ */
27
+ export declare const AparteUi: import("react").ForwardRefExoticComponent<AparteUiProps & import("react").RefAttributes<AparteUiHandle>>;
28
+ //# sourceMappingURL=AparteUi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AparteUi.d.ts","sourceRoot":"","sources":["../../src/components/AparteUi.tsx"],"names":[],"mappings":"AAGA,MAAM,WAAW,aAAa;IAC1B,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,wEAAwE;IACxE,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IAC9C;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC3B,UAAU,EAAE,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,OAAO,CAAC,GAAG,IAAI,CAAC;IAChE,UAAU,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,GAAG,SAAS,CAAC;CACtF;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ,0GAgDnB,CAAC"}
@@ -0,0 +1,44 @@
1
+ import type { AparteMessage, AparteSegment } from '../types.js';
2
+ import type { AparteChatHandle } from '../components/AparteChat.js';
3
+ /**
4
+ * Idiomatic React ergonomics for `<AparteChat>`. Owns the `messages` state and
5
+ * the component ref so the consumer doesn't have to wire `onMessagesChange`
6
+ * back into `messages` by hand. Spread `ref` + `messages` + `onMessagesChange`
7
+ * onto `<AparteChat>` and drive it with the returned imperative helpers.
8
+ *
9
+ * @example
10
+ * const chat = useAparteChat();
11
+ * return (
12
+ * <AparteChat
13
+ * ref={chat.ref}
14
+ * messages={chat.messages}
15
+ * onMessagesChange={chat.setMessages}
16
+ * />
17
+ * );
18
+ * // The user's message is appended automatically on send — don't re-add it.
19
+ */
20
+ export interface UseAparteChat {
21
+ messages: AparteMessage[];
22
+ setMessages: React.Dispatch<React.SetStateAction<AparteMessage[]>>;
23
+ ref: React.RefObject<AparteChatHandle>;
24
+ appendMessage: (message: AparteMessage) => void;
25
+ updateMessage: (messageId: string, updates: Partial<AparteMessage>) => void;
26
+ updateLastMessage: (content: string, options?: {
27
+ append?: boolean;
28
+ }) => void;
29
+ addSegment: (segment: AparteSegment) => void;
30
+ updateSegment: (segmentId: string, updates: Partial<AparteSegment>) => void;
31
+ removeSegment: (segmentId: string) => void;
32
+ appendToSegment: (segmentId: string, content: string) => void;
33
+ clearMessages: () => void;
34
+ addBranch: (messageId: string) => number;
35
+ addSiblingOf: (existingId: string, message: AparteMessage) => string | null;
36
+ truncateFrom: (messageId: string) => void;
37
+ truncateResponsesAfter: (userMessageId: string) => void;
38
+ injectTokenStream: (messageId: string, tokens: AsyncIterable<string>) => Promise<void>;
39
+ stopTokenStream: () => void;
40
+ setConversationId: (id: string | null) => Promise<void>;
41
+ isStreaming: () => boolean;
42
+ }
43
+ export declare function useAparteChat(initial?: AparteMessage[]): UseAparteChat;
44
+ //# sourceMappingURL=useAparteChat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAparteChat.d.ts","sourceRoot":"","sources":["../../src/hooks/useAparteChat.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAEpE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,aAAa;IAC1B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;IAKnE,GAAG,EAAE,KAAK,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;IAEvC,aAAa,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAChD,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,KAAK,IAAI,CAAC;IAC5E,iBAAiB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;IAC7E,UAAU,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAC7C,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,KAAK,IAAI,CAAC;IAC5E,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,eAAe,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9D,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IACzC,YAAY,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,KAAK,MAAM,GAAG,IAAI,CAAC;IAC5E,YAAY,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,sBAAsB,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD,iBAAiB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvF,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,iBAAiB,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,WAAW,EAAE,MAAM,OAAO,CAAC;CAC9B;AAED,wBAAgB,aAAa,CAAC,OAAO,GAAE,aAAa,EAAO,GAAG,aAAa,CAyB1E"}
@@ -0,0 +1,17 @@
1
+ import { AparteClient, type AparteClientOptions } from '@aparte/core';
2
+ export interface UseAparteClient {
3
+ /** The underlying agnostic client. */
4
+ client: AparteClient;
5
+ /** Abort the current AI response + all active tool calls. */
6
+ abort: () => void;
7
+ }
8
+ /**
9
+ * Mounts an `AparteClient` that bridges `aparte-send` events to the configured AI
10
+ * providers. Starts listening on mount, stops on unmount. React equivalent of
11
+ * Angular's `AparteAiService`.
12
+ *
13
+ * @example
14
+ * const { abort } = useAparteClient({ keyResolver });
15
+ */
16
+ export declare function useAparteClient(options?: AparteClientOptions): UseAparteClient;
17
+ //# sourceMappingURL=useAparteClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAparteClient.d.ts","sourceRoot":"","sources":["../../src/hooks/useAparteClient.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEtE,MAAM,WAAW,eAAe;IAC5B,sCAAsC;IACtC,MAAM,EAAE,YAAY,CAAC;IACrB,6DAA6D;IAC7D,KAAK,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,eAAe,CAS9E"}
@@ -0,0 +1,27 @@
1
+ import { type AparteConversation, type AparteStorageAdapter } from '@aparte/core';
2
+ import type { AparteMessage } from '../types.js';
3
+ export interface UseConversationManager {
4
+ conversations: AparteConversation[];
5
+ /** Active conversations, newest first. */
6
+ activeConversations: AparteConversation[];
7
+ /** Archived conversations, newest first. */
8
+ archivedConversations: AparteConversation[];
9
+ activeId: string | null;
10
+ activeConversation: AparteConversation | null;
11
+ /** Initialise with a storage adapter (call once). */
12
+ init: (adapter: AparteStorageAdapter) => Promise<void>;
13
+ createNew: (title?: string) => Promise<AparteConversation>;
14
+ addMessage: (convId: string, message: AparteMessage) => Promise<void>;
15
+ updateMessages: (convId: string, messages: AparteMessage[]) => Promise<void>;
16
+ delete: (id: string) => Promise<void>;
17
+ archive: (id: string) => Promise<void>;
18
+ unarchive: (id: string) => Promise<void>;
19
+ }
20
+ /**
21
+ * React-state wrapper around the core `ConversationManager`. The active
22
+ * conversation is owned by the chat component's controller; switch by binding
23
+ * `conversationId` on `<AparteChat>`. React equivalent of Angular's
24
+ * `ConversationManagerService`.
25
+ */
26
+ export declare function useConversationManager(): UseConversationManager;
27
+ //# sourceMappingURL=useConversationManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useConversationManager.d.ts","sourceRoot":"","sources":["../../src/hooks/useConversationManager.ts"],"names":[],"mappings":"AACA,OAAO,EAGH,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EAC5B,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,WAAW,sBAAsB;IACnC,aAAa,EAAE,kBAAkB,EAAE,CAAC;IACpC,0CAA0C;IAC1C,mBAAmB,EAAE,kBAAkB,EAAE,CAAC;IAC1C,4CAA4C;IAC5C,qBAAqB,EAAE,kBAAkB,EAAE,CAAC;IAC5C,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,kBAAkB,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC9C,qDAAqD;IACrD,IAAI,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,SAAS,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC3D,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7E,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,IAAI,sBAAsB,CAsD/D"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * aparté React wrapper
3
+ * React 18/19 integration with hooks and segment support.
4
+ */
5
+ export { AparteChat } from './components/AparteChat.js';
6
+ export type { AparteChatProps, AparteChatHandle } from './components/AparteChat.js';
7
+ export { useAparteChat } from './hooks/useAparteChat.js';
8
+ export type { UseAparteChat } from './hooks/useAparteChat.js';
9
+ export { useAparteClient } from './hooks/useAparteClient.js';
10
+ export type { UseAparteClient } from './hooks/useAparteClient.js';
11
+ export { useConversationManager } from './hooks/useConversationManager.js';
12
+ export type { UseConversationManager } from './hooks/useConversationManager.js';
13
+ export { AparteUi } from './components/AparteUi.js';
14
+ export type { AparteUiProps, AparteUiHandle } from './components/AparteUi.js';
15
+ export type { AparteMessage, AparteSendEventDetail, AparteActionEventDetail, AparteSegment, AparteTextSegment, AparteCodeSegment, AparteThinkingSegment, AparteTerminalSegment, } from './types.js';
16
+ declare global {
17
+ namespace JSX {
18
+ interface IntrinsicElements {
19
+ 'aparte-chat-viewport': any;
20
+ 'aparte-chat-bubble': any;
21
+ 'aparte-chat-status': any;
22
+ 'aparte-composer': any;
23
+ 'aparte-composer-attachments': any;
24
+ 'aparte-composer-add-attachment': any;
25
+ 'aparte-composer-input': any;
26
+ 'aparte-composer-send': any;
27
+ }
28
+ }
29
+ }
30
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAGpF,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,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE9E,YAAY,EACR,aAAa,EACb,qBAAqB,EACrB,uBAAuB,EACvB,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,GACxB,MAAM,YAAY,CAAC;AAGpB,OAAO,CAAC,MAAM,CAAC;IAEX,UAAU,GAAG,CAAC;QACV,UAAU,iBAAiB;YAEvB,sBAAsB,EAAE,GAAG,CAAC;YAE5B,oBAAoB,EAAE,GAAG,CAAC;YAE1B,oBAAoB,EAAE,GAAG,CAAC;YAE1B,iBAAiB,EAAE,GAAG,CAAC;YAEvB,6BAA6B,EAAE,GAAG,CAAC;YAEnC,gCAAgC,EAAE,GAAG,CAAC;YAEtC,uBAAuB,EAAE,GAAG,CAAC;YAE7B,sBAAsB,EAAE,GAAG,CAAC;SAC/B;KACJ;CACJ"}
package/dist/index.js ADDED
@@ -0,0 +1,323 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import React, { forwardRef, useId, useRef, useState, useEffect, useImperativeHandle, useCallback, useMemo } from "react";
3
+ import { AparteChatHost, AparteClient, ConversationManager, AparteConfig, DEFAULT_UI_EVENTS, applyElementProps } from "@aparte/core";
4
+ const AparteChat = forwardRef(function AparteChat2({
5
+ messages = [],
6
+ placeholder = "Type a message...",
7
+ disabled = false,
8
+ isTyping = false,
9
+ typingText = "Assistant is thinking...",
10
+ submitOnEnter = true,
11
+ layoutTransitionMs = 0,
12
+ centerWhenEmpty = false,
13
+ conversationId = null,
14
+ composer,
15
+ renderBubble,
16
+ emptyState,
17
+ aboveComposer,
18
+ footerLeft,
19
+ footerCenter,
20
+ footerRight,
21
+ config,
22
+ onMessageSent,
23
+ onAction,
24
+ onMessagesChange,
25
+ onMessageAppended,
26
+ onTypingChange,
27
+ onConversationCreated
28
+ }, ref) {
29
+ const hostId = `aparte-chat-${useId().replace(/:/g, "")}`;
30
+ const hostElRef = useRef(null);
31
+ const viewportRef = useRef(null);
32
+ const composerRef = useRef(null);
33
+ const messagesRef = useRef(messages);
34
+ const [renderMessages, setRenderMessages] = useState(messages);
35
+ const [typingActive, setTypingActive] = useState(isTyping);
36
+ const [, setIsStreaming] = useState(false);
37
+ const hostRef = useRef(null);
38
+ const lastConvRef = useRef(conversationId);
39
+ const cbRef = useRef({ onMessageSent, onAction, onMessagesChange, onMessageAppended, onTypingChange, onConversationCreated });
40
+ cbRef.current = { onMessageSent, onAction, onMessagesChange, onMessageAppended, onTypingChange, onConversationCreated };
41
+ const applyMessages = (m) => {
42
+ messagesRef.current = m;
43
+ setRenderMessages(m);
44
+ };
45
+ useEffect(() => {
46
+ const host = hostElRef.current;
47
+ if (!host) return;
48
+ const binding = {
49
+ hostId,
50
+ host,
51
+ viewport: viewportRef.current,
52
+ getMessages: () => messagesRef.current,
53
+ setMessages: (m) => applyMessages(m),
54
+ onMessagesChange: (m) => cbRef.current.onMessagesChange?.(m),
55
+ onMessageAppended: (m) => cbRef.current.onMessageAppended?.(m),
56
+ onTypingChange: (t) => {
57
+ setTypingActive(t);
58
+ cbRef.current.onTypingChange?.(t);
59
+ },
60
+ onStreamingChange: (id) => setIsStreaming(id !== null),
61
+ afterRender: (cb) => {
62
+ requestAnimationFrame(() => cb());
63
+ },
64
+ resetComposer: () => composerRef.current?.reset?.()
65
+ };
66
+ const h = new AparteChatHost(binding, {
67
+ layoutTransitionMs,
68
+ conversationId: conversationId ?? null,
69
+ onConversationCreated: (id) => cbRef.current.onConversationCreated?.(id),
70
+ config
71
+ });
72
+ hostRef.current = h;
73
+ const teardown = h.bind();
74
+ return () => {
75
+ teardown();
76
+ hostRef.current = null;
77
+ };
78
+ }, [hostId]);
79
+ useEffect(() => {
80
+ if (messages === messagesRef.current) return;
81
+ applyMessages(messages);
82
+ if (messages.length === 0) hostRef.current?.clearRenderCache();
83
+ }, [messages]);
84
+ useEffect(() => {
85
+ hostRef.current?.syncBubbles();
86
+ }, [renderMessages]);
87
+ useEffect(() => {
88
+ setTypingActive(isTyping);
89
+ }, [isTyping]);
90
+ useEffect(() => {
91
+ if (conversationId === lastConvRef.current) return;
92
+ lastConvRef.current = conversationId;
93
+ void hostRef.current?.setConversationId(conversationId ?? null);
94
+ }, [conversationId]);
95
+ useEffect(() => {
96
+ const composer2 = composerRef.current;
97
+ if (!composer2) return;
98
+ const onSend = (e) => {
99
+ viewportRef.current?.requestSmoothScroll?.();
100
+ cbRef.current.onMessageSent?.(e.detail);
101
+ };
102
+ composer2.addEventListener("aparte-send", onSend);
103
+ return () => composer2.removeEventListener("aparte-send", onSend);
104
+ }, []);
105
+ useEffect(() => {
106
+ const composer2 = composerRef.current;
107
+ if (!composer2) return;
108
+ composer2.setAttribute("placeholder", placeholder);
109
+ if (disabled) composer2.setAttribute("disabled", "");
110
+ else composer2.removeAttribute("disabled");
111
+ }, [placeholder, disabled]);
112
+ useEffect(() => {
113
+ const host = hostElRef.current;
114
+ if (!host) return;
115
+ const onAct = (e) => cbRef.current.onAction?.(e.detail);
116
+ host.addEventListener("aparte-action", onAct);
117
+ return () => host.removeEventListener("aparte-action", onAct);
118
+ }, []);
119
+ useImperativeHandle(ref, () => ({
120
+ appendMessage: (m) => hostRef.current?.appendMessage(m),
121
+ updateMessage: (id, u) => hostRef.current?.updateMessage(id, u),
122
+ updateLastMessage: (c, o) => hostRef.current?.updateLastMessage(c, o),
123
+ addSegment: (s) => hostRef.current?.addSegment(s),
124
+ updateSegment: (id, u) => hostRef.current?.updateSegment(id, u),
125
+ removeSegment: (id) => hostRef.current?.removeSegment(id),
126
+ appendToSegment: (id, c) => hostRef.current?.appendToSegment(id, c),
127
+ getMessages: () => hostRef.current?.getMessages() ?? messagesRef.current,
128
+ clearMessages: () => hostRef.current?.clearMessages(),
129
+ addBranch: (id) => hostRef.current?.addBranch(id) ?? 0,
130
+ addSiblingOf: (id, m) => hostRef.current?.addSiblingOf(id, m) ?? null,
131
+ truncateFrom: (id) => hostRef.current?.truncateFrom(id),
132
+ truncateResponsesAfter: (id) => hostRef.current?.truncateResponsesAfter(id),
133
+ injectTokenStream: (id, tokens) => hostRef.current?.streamTokens(id, tokens) ?? Promise.resolve(),
134
+ stopTokenStream: () => hostRef.current?.stopTokenStream(),
135
+ setConversationId: (id) => hostRef.current?.setConversationId(id) ?? Promise.resolve(),
136
+ scrollToBottom: () => viewportRef.current?.scrollToBottom?.(),
137
+ focusInput: () => composerRef.current?.focus?.(),
138
+ isStreaming: () => hostRef.current?.isStreaming ?? false,
139
+ getViewport: () => viewportRef.current
140
+ }), []);
141
+ return /* @__PURE__ */ jsxs(
142
+ "div",
143
+ {
144
+ className: `aparte-chat-container${centerWhenEmpty ? " aparte-chat-container--auto-center" : ""}`,
145
+ "data-aparte-chat": "",
146
+ "data-aparte-empty": centerWhenEmpty && renderMessages.length === 0 ? "" : void 0,
147
+ id: hostId,
148
+ ref: hostElRef,
149
+ children: [
150
+ /* @__PURE__ */ jsxs("aparte-chat-viewport", { ref: viewportRef, "framework-managed": "", children: [
151
+ renderMessages.length === 0 && emptyState,
152
+ renderMessages.map((m) => renderBubble ? /* @__PURE__ */ jsx(React.Fragment, { children: renderBubble(m) }, m.id) : /* @__PURE__ */ jsx(
153
+ "aparte-chat-bubble",
154
+ {
155
+ "message-id": m.id,
156
+ "data-role": m.role,
157
+ timestamp: m.timestamp,
158
+ content: m.content,
159
+ streaming: m.status === "streaming" || m.status === "pending" ? "" : void 0
160
+ },
161
+ m.id
162
+ )),
163
+ /* @__PURE__ */ jsx("aparte-chat-status", { visible: typingActive ? "" : void 0, text: typingText })
164
+ ] }),
165
+ aboveComposer,
166
+ /* @__PURE__ */ jsx(
167
+ "aparte-composer",
168
+ {
169
+ ref: composerRef,
170
+ target: hostId,
171
+ "submit-on-enter": submitOnEnter ? void 0 : "false",
172
+ children: composer ?? /* @__PURE__ */ jsxs("div", { className: "aparte-composer-shell", children: [
173
+ /* @__PURE__ */ jsx("aparte-composer-attachments", {}),
174
+ /* @__PURE__ */ jsxs("div", { className: "aparte-composer-row", children: [
175
+ /* @__PURE__ */ jsx("aparte-composer-add-attachment", {}),
176
+ /* @__PURE__ */ jsx("aparte-composer-input", {}),
177
+ /* @__PURE__ */ jsx("aparte-composer-send", {})
178
+ ] }),
179
+ (footerLeft != null || footerCenter != null || footerRight != null) && /* @__PURE__ */ jsxs("div", { className: "aparte-composer-footer", children: [
180
+ footerLeft,
181
+ footerCenter,
182
+ footerRight
183
+ ] })
184
+ ] })
185
+ }
186
+ )
187
+ ]
188
+ }
189
+ );
190
+ });
191
+ AparteChat.displayName = "AparteChat";
192
+ function useAparteChat(initial = []) {
193
+ const [messages, setMessages] = useState(initial);
194
+ const ref = useRef(null);
195
+ const h = () => ref.current;
196
+ return {
197
+ messages,
198
+ setMessages,
199
+ ref,
200
+ appendMessage: (m) => h()?.appendMessage(m),
201
+ updateMessage: (id, u) => h()?.updateMessage(id, u),
202
+ updateLastMessage: (c, o) => h()?.updateLastMessage(c, o),
203
+ addSegment: (s) => h()?.addSegment(s),
204
+ updateSegment: (id, u) => h()?.updateSegment(id, u),
205
+ removeSegment: (id) => h()?.removeSegment(id),
206
+ appendToSegment: (id, c) => h()?.appendToSegment(id, c),
207
+ clearMessages: () => h()?.clearMessages(),
208
+ addBranch: (id) => h()?.addBranch(id) ?? 0,
209
+ addSiblingOf: (id, m) => h()?.addSiblingOf(id, m) ?? null,
210
+ truncateFrom: (id) => h()?.truncateFrom(id),
211
+ truncateResponsesAfter: (id) => h()?.truncateResponsesAfter(id),
212
+ injectTokenStream: (id, tokens) => h()?.injectTokenStream(id, tokens) ?? Promise.resolve(),
213
+ stopTokenStream: () => h()?.stopTokenStream(),
214
+ setConversationId: (id) => h()?.setConversationId(id) ?? Promise.resolve(),
215
+ isStreaming: () => h()?.isStreaming() ?? false
216
+ };
217
+ }
218
+ function useAparteClient(options) {
219
+ const ref = useRef(null);
220
+ if (!ref.current) ref.current = new AparteClient(options ?? {});
221
+ useEffect(() => {
222
+ const c = ref.current;
223
+ c.start();
224
+ return () => c.stop();
225
+ }, []);
226
+ return { client: ref.current, abort: () => ref.current?.abort() };
227
+ }
228
+ function useConversationManager() {
229
+ const managerRef = useRef(null);
230
+ const unsubRef = useRef(null);
231
+ const [conversations, setConversations] = useState([]);
232
+ const [activeId, setActiveId] = useState(null);
233
+ useEffect(() => () => unsubRef.current?.(), []);
234
+ const init = useCallback(async (adapter) => {
235
+ const m = new ConversationManager(adapter);
236
+ managerRef.current = m;
237
+ unsubRef.current = m.subscribe((convs) => {
238
+ setConversations([...convs]);
239
+ setActiveId(m.activeId);
240
+ });
241
+ await m.init();
242
+ setActiveId(m.activeId);
243
+ AparteConfig.setConversationManager(m);
244
+ }, []);
245
+ const assert = () => {
246
+ if (!managerRef.current) {
247
+ throw new Error("[useConversationManager] Not initialised. Call init(adapter) first.");
248
+ }
249
+ return managerRef.current;
250
+ };
251
+ const activeConversations = useMemo(
252
+ () => conversations.filter((c) => !c.archivedAt).sort((a, b) => b.updatedAt - a.updatedAt),
253
+ [conversations]
254
+ );
255
+ const archivedConversations = useMemo(
256
+ () => conversations.filter((c) => !!c.archivedAt).sort((a, b) => b.updatedAt - a.updatedAt),
257
+ [conversations]
258
+ );
259
+ const activeConversation = useMemo(
260
+ () => activeId ? conversations.find((c) => c.id === activeId) ?? null : null,
261
+ [conversations, activeId]
262
+ );
263
+ return {
264
+ conversations,
265
+ activeConversations,
266
+ archivedConversations,
267
+ activeId,
268
+ activeConversation,
269
+ init,
270
+ createNew: (title) => assert().createNew(title),
271
+ addMessage: (convId, message) => assert().addMessage(convId, message),
272
+ updateMessages: (convId, messages) => assert().updateMessages(convId, messages),
273
+ delete: (id) => assert().delete(id),
274
+ archive: (id) => assert().archive(id),
275
+ unarchive: (id) => assert().unarchive(id)
276
+ };
277
+ }
278
+ const AparteUi = forwardRef(function AparteUi2({ name, props = {}, onElementEvent, events }, ref) {
279
+ const hostRef = useRef(null);
280
+ const elRef = useRef(null);
281
+ const cbRef = useRef(onElementEvent);
282
+ cbRef.current = onElementEvent;
283
+ const evts = events ?? DEFAULT_UI_EVENTS;
284
+ const evtsKey = evts.join("|");
285
+ useEffect(() => {
286
+ const host = hostRef.current;
287
+ if (!host) return;
288
+ const el = document.createElement(name);
289
+ elRef.current = el;
290
+ const cleanups = [];
291
+ for (const ev of evtsKey.split("|").filter(Boolean)) {
292
+ const listener = (e) => cbRef.current?.(e);
293
+ el.addEventListener(ev, listener);
294
+ cleanups.push(() => el.removeEventListener(ev, listener));
295
+ }
296
+ host.appendChild(el);
297
+ return () => {
298
+ for (const c of cleanups) c();
299
+ el.remove();
300
+ elRef.current = null;
301
+ };
302
+ }, [name, evtsKey]);
303
+ useEffect(() => {
304
+ if (elRef.current) applyElementProps(elRef.current, props);
305
+ }, [props]);
306
+ useImperativeHandle(ref, () => ({
307
+ getElement: () => elRef.current,
308
+ callMethod: (methodName, ...args) => {
309
+ const fn = elRef.current?.[methodName];
310
+ return typeof fn === "function" ? fn.apply(elRef.current, args) : void 0;
311
+ }
312
+ }), []);
313
+ return /* @__PURE__ */ jsx("span", { ref: hostRef, style: { display: "contents" } });
314
+ });
315
+ AparteUi.displayName = "AparteUi";
316
+ export {
317
+ AparteChat,
318
+ AparteUi,
319
+ useAparteChat,
320
+ useAparteClient,
321
+ useConversationManager
322
+ };
323
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/components/AparteChat.tsx","../src/hooks/useAparteChat.ts","../src/hooks/useAparteClient.ts","../src/hooks/useConversationManager.ts","../src/components/AparteUi.tsx"],"sourcesContent":["'use client';\n\nimport React, {\n useEffect,\n useId,\n useRef,\n useState,\n forwardRef,\n useImperativeHandle,\n} from 'react';\nimport { AparteChatHost, type AparteChatHostBinding, type AparteConfigClass, type AparteChatImperativeApi } from '@aparte/core';\nimport type { AparteMessage, AparteSendEventDetail, AparteActionEventDetail } from '../types.js';\n\nexport interface AparteChatProps {\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 * Active conversation id. When set, the wrapper loads/persists via the\n * `ConversationManager` registered in `AparteConfig` (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 (add-attachment · input · send). 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 /** Footer content, left slot — rendered in the default shell's footer row. */\n footerLeft?: React.ReactNode;\n /** Footer content, center slot. */\n footerCenter?: React.ReactNode;\n /** Footer content, right slot (e.g. a model selector or token counter). */\n footerRight?: 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 * `AparteConfig.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 AparteConfigClass} for this chat. When set, every aparté\n * component rendered inside resolves THIS config instead of the global\n * `AparteConfig` singleton — 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?: AparteConfigClass;\n}\n\n/**\n * The React ref handle for `<AparteChat>` — the canonical imperative surface\n * shared by all four wrappers (see `AparteChatImperativeApi` in `@aparte/core`).\n */\nexport type AparteChatHandle = AparteChatImperativeApi;\n\nexport const AparteChat = forwardRef<AparteChatHandle, AparteChatProps>(function AparteChat(\n {\n messages = [],\n placeholder = 'Type a message...',\n disabled = false,\n isTyping = false,\n typingText = 'Assistant is thinking...',\n submitOnEnter = true,\n layoutTransitionMs = 0,\n centerWhenEmpty = false,\n conversationId = null,\n composer,\n renderBubble,\n emptyState,\n aboveComposer,\n footerLeft,\n footerCenter,\n footerRight,\n config,\n onMessageSent,\n onAction,\n onMessagesChange,\n onMessageAppended,\n onTypingChange,\n onConversationCreated,\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 const hostId = `aparte-chat-${useId().replace(/:/g, '')}`;\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 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.\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) => 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: (cb) => { requestAnimationFrame(() => cb()); },\n resetComposer: () => (composerRef.current as unknown as { reset?: () => void })?.reset?.(),\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. Guarded by ref\n // identity so the host's own emit→parent→prop round-trip doesn't loop.\n useEffect(() => {\n if (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 // aparte-composer exposes `placeholder`/`disabled` as GETTER-ONLY accessors.\n // React 19 sets matching props as PROPERTIES on custom elements, which throws\n // (\"Cannot set property placeholder ... which has only a getter\"). Set them as\n // attributes imperatively instead (the getter reads the attribute).\n useEffect(() => {\n const composer = composerRef.current;\n if (!composer) return;\n composer.setAttribute('placeholder', placeholder);\n if (disabled) composer.setAttribute('disabled', '');\n else composer.removeAttribute('disabled');\n }, [placeholder, disabled]);\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, (): AparteChatHandle => ({\n appendMessage: (m) => hostRef.current?.appendMessage(m),\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: () => hostRef.current?.clearMessages(),\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' : ''}`}\n data-aparte-chat=\"\"\n data-aparte-empty={centerWhenEmpty && renderMessages.length === 0 ? '' : undefined}\n id={hostId}\n ref={hostElRef}\n >\n <aparte-chat-viewport ref={viewportRef as React.Ref<HTMLElement>} framework-managed=\"\">\n {renderMessages.length === 0 && 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 timestamp={m.timestamp}\n content={m.content}\n streaming={m.status === 'streaming' || m.status === 'pending' ? '' : undefined}\n />\n )\n ))}\n <aparte-chat-status visible={typingActive ? '' : undefined} text={typingText} />\n </aparte-chat-viewport>\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 <aparte-composer-attachments />\n <div className=\"aparte-composer-row\">\n <aparte-composer-add-attachment />\n <aparte-composer-input />\n <aparte-composer-send />\n </div>\n {(footerLeft != null || footerCenter != null || footerRight != null) && (\n <div className=\"aparte-composer-footer\">\n {footerLeft}\n {footerCenter}\n {footerRight}\n </div>\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 { AparteChatHandle } from '../components/AparteChat.js';\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<AparteChatHandle>;\n // ── imperative helpers (delegate to the component handle) ──\n appendMessage: (message: AparteMessage) => 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 clearMessages: () => 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}\n\nexport function useAparteChat(initial: AparteMessage[] = []): UseAparteChat {\n const [messages, setMessages] = useState<AparteMessage[]>(initial);\n const ref = useRef<AparteChatHandle | null>(null);\n const h = () => ref.current;\n return {\n messages,\n setMessages,\n ref: ref as React.RefObject<AparteChatHandle>,\n appendMessage: (m) => h()?.appendMessage(m),\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 clearMessages: () => h()?.clearMessages(),\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 };\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 * @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 AparteConfig,\n ConversationManager,\n type AparteConversation,\n type AparteStorageAdapter,\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 /** Initialise with a storage adapter (call once). */\n init: (adapter: AparteStorageAdapter) => Promise<void>;\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}\n\n/**\n * React-state wrapper around the core `ConversationManager`. 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<ConversationManager | 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 const init = useCallback(async (adapter: AparteStorageAdapter) => {\n const m = new ConversationManager(adapter);\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 AparteConfig.setConversationManager(m);\n }, []);\n\n const assert = (): ConversationManager => {\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 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 };\n}\n","import { useEffect, useRef, forwardRef, useImperativeHandle } from 'react';\nimport { applyElementProps, DEFAULT_UI_EVENTS } 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 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\nexport interface AparteUiHandle {\n getElement: <T extends HTMLElement = HTMLElement>() => T | null;\n callMethod: <T = unknown>(methodName: string, ...args: unknown[]) => T | undefined;\n}\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 * @example\n * <AparteUi name=\"aparte-model-selector\" props={{ placeholder: 'Ask…', '--glow-speed': '4s' }} onElementEvent={onEvent} />\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\n const evts = events ?? 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 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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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Public types for the React wrapper — all re-exported from `@aparte/core`, the
3
+ * single source of truth. `AparteSendEventDetail` used to be re-declared here
4
+ * WITHOUT `targetId`, which the composer actually sends (multi-instance scoping);
5
+ * re-export the canonical one so the field isn't silently dropped from the type.
6
+ */
7
+ export type { AparteMessage, AparteSegment, AparteTextSegment, AparteCodeSegment, AparteThinkingSegment, AparteTerminalSegment, AparteSendEventDetail, AparteActionEventDetail, } from '@aparte/core';
8
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,YAAY,EACR,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,uBAAuB,GAC1B,MAAM,cAAc,CAAC"}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@aparte/react",
3
+ "version": "0.2.0-alpha.0",
4
+ "description": "React 18+ wrapper for aparté — an ergonomic <AparteChat> component plus hooks over the framework-agnostic web components.",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "@aparte-workspace/source": "./src/index.ts",
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "default": "./dist/index.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
23
+ "engines": {
24
+ "node": ">=18"
25
+ },
26
+ "peerDependencies": {
27
+ "react": "^18.0.0 || ^19.0.0",
28
+ "react-dom": "^18.0.0 || ^19.0.0",
29
+ "@aparte/core": "0.2.0-alpha.0"
30
+ },
31
+ "devDependencies": {
32
+ "@testing-library/react": "^16.1.0",
33
+ "@types/react": "^18.3.0",
34
+ "@types/react-dom": "^18.3.0",
35
+ "@vitejs/plugin-react": "^4.3.0",
36
+ "jsdom": "^22.1.0",
37
+ "react": "^18.3.0",
38
+ "react-dom": "^18.3.0",
39
+ "typescript": "^5.4.0",
40
+ "vite": "^6.0.0",
41
+ "@aparte/core": "0.2.0-alpha.0"
42
+ },
43
+ "keywords": [
44
+ "react",
45
+ "aparte",
46
+ "chat",
47
+ "ai",
48
+ "web-components"
49
+ ],
50
+ "license": "MIT",
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "git+https://github.com/apartejs/aparte.git",
54
+ "directory": "packages/wrappers/react"
55
+ },
56
+ "bugs": {
57
+ "url": "https://github.com/apartejs/aparte/issues"
58
+ },
59
+ "scripts": {
60
+ "dev": "vite",
61
+ "build": "vite build && tsc -b --emitDeclarationOnly --force",
62
+ "preview": "vite preview",
63
+ "test": "vitest",
64
+ "test:run": "vitest run",
65
+ "test:coverage": "vitest run --coverage"
66
+ }
67
+ }