@aparte/angular 0.24.0 → 0.26.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.
@@ -11,7 +11,7 @@
11
11
  * a plain host listener when you need `stopPropagation`.
12
12
  */
13
13
  import { ElementRef, EventEmitter } from '@angular/core';
14
- import type { AparteActionClickEventDetail, AparteActionEventDetail, AparteAttachmentPreviewEventDetail, AparteBranchNavigateEventDetail, AparteCompactEventDetail, AparteComposerChangeEventDetail, AparteContextThresholdEventDetail, AparteConversationArchiveDetail, AparteConversationDeleteDetail, AparteConversationPinDetail, AparteConversationRenameDetail, AparteConversationSelectDetail, AparteEditEventDetail, AparteFeedbackEventDetail, AparteLinkClickEventDetail, AparteMessageInfoEventDetail, AparteOptgroupToggleEventDetail, ApartePathChangedEventDetail, AparteRetryEventDetail, AparteScrollRailJumpDetail, AparteSegmentUpdateEventDetail, AparteSelectChangeDetail, AparteSendEventDetail, AparteSidebarToggleDetail, AparteSplitResizeDetail, AparteSuggestionEventDetail } from '@aparte/core';
14
+ import type { AparteActionClickEventDetail, AparteActionEventDetail, AparteAttachmentPreviewEventDetail, AparteBranchNavigateEventDetail, AparteCompactEventDetail, AparteComposerChangeEventDetail, AparteContextThresholdEventDetail, AparteConversationArchiveDetail, AparteConversationDeleteDetail, AparteConversationListItem, AparteConversationPinDetail, AparteConversationRenameDetail, AparteConversationSelectDetail, AparteEditEventDetail, AparteFeedbackEventDetail, AparteLinkClickEventDetail, AparteMessageInfoEventDetail, AparteOptgroupToggleEventDetail, ApartePathChangedEventDetail, AparteRetryEventDetail, AparteScrollRailJumpDetail, AparteSegmentUpdateEventDetail, AparteSelectChangeDetail, AparteSendEventDetail, AparteSidebarToggleDetail, AparteSplitResizeDetail, AparteSuggestionEventDetail } from '@aparte/core';
15
15
  import * as i0 from "@angular/core";
16
16
  /**
17
17
  * Writes one declared attribute onto the host element.
@@ -41,6 +41,8 @@ export declare class AparteChatBubbleDirective extends AparteElementBase {
41
41
  set streaming(v: boolean);
42
42
  /** Who wrote the message: shown in the header, and the message's accessible name ("Message from {name}"). Leave it out for the reader and the assistant, whose names come from the locale. */
43
43
  set name(v: string | undefined);
44
+ /** Why the compaction failed, beside `data-checkpoint="failed"`: the separator gives it as the failure's tooltip and to screen readers. Set and cleared with `data-checkpoint`; read it, do not write it. */
45
+ set dataCheckpointReason(v: string | undefined);
44
46
  /** An attached image was clicked, asking the app to open it full-size. */
45
47
  readonly attachmentPreview: EventEmitter<AparteAttachmentPreviewEventDetail>;
46
48
  /** A custom action-bar button was pressed. */
@@ -66,7 +68,7 @@ export declare class AparteChatBubbleDirective extends AparteElementBase {
66
68
  protected onBranchNavigate(e: CustomEvent<AparteBranchNavigateEventDetail>): void;
67
69
  protected onLinkClick(e: CustomEvent<AparteLinkClickEventDetail>): void;
68
70
  static ɵfac: i0.ɵɵFactoryDeclaration<AparteChatBubbleDirective, never>;
69
- static ɵdir: i0.ɵɵDirectiveDeclaration<AparteChatBubbleDirective, "aparte-chat-bubble", never, { "messageRole": { "alias": "messageRole"; "required": false; }; "content": { "alias": "content"; "required": false; }; "timestamp": { "alias": "timestamp"; "required": false; }; "messageId": { "alias": "messageId"; "required": false; }; "streaming": { "alias": "streaming"; "required": false; }; "name": { "alias": "name"; "required": false; }; }, { "attachmentPreview": "attachmentPreview"; "action": "action"; "retry": "retry"; "feedback": "feedback"; "messageInfo": "messageInfo"; "edit": "edit"; "branchNavigate": "branchNavigate"; "linkClick": "linkClick"; }, never, never, true, never>;
71
+ static ɵdir: i0.ɵɵDirectiveDeclaration<AparteChatBubbleDirective, "aparte-chat-bubble", never, { "messageRole": { "alias": "messageRole"; "required": false; }; "content": { "alias": "content"; "required": false; }; "timestamp": { "alias": "timestamp"; "required": false; }; "messageId": { "alias": "messageId"; "required": false; }; "streaming": { "alias": "streaming"; "required": false; }; "name": { "alias": "name"; "required": false; }; "dataCheckpointReason": { "alias": "dataCheckpointReason"; "required": false; }; }, { "attachmentPreview": "attachmentPreview"; "action": "action"; "retry": "retry"; "feedback": "feedback"; "messageInfo": "messageInfo"; "edit": "edit"; "branchNavigate": "branchNavigate"; "linkClick": "linkClick"; }, never, never, true, never>;
70
72
  static ngAcceptInputType_streaming: unknown;
71
73
  }
72
74
  /** `<aparte-chat-status>` — A standalone status line — a light-DOM indicator the APP owns. Nothing in core turns it on: the framework host only ever flips it back OFF (on the first streamed token), and the four wrappers render one inside the viewport driven by their own `isTyping` prop. */
@@ -230,7 +232,7 @@ export declare class AparteComposerToolbarDirective {
230
232
  static ɵfac: i0.ɵɵFactoryDeclaration<AparteComposerToolbarDirective, never>;
231
233
  static ɵdir: i0.ɵɵDirectiveDeclaration<AparteComposerToolbarDirective, "aparte-composer-toolbar", never, {}, {}, never, never, true, never>;
232
234
  }
233
- /** `<aparte-context>` — A gauge of the model's context window: how much of it the conversation uses. */
235
+ /** `<aparte-context>` — A gauge of the model's context window: how much of it the conversation uses, and what fills it. */
234
236
  export declare class AparteContextDirective extends AparteElementBase {
235
237
  /** The context window, in tokens. Overrides the current model's `contextWindow`. */
236
238
  set window(v: number);
@@ -244,20 +246,22 @@ export declare class AparteContextDirective extends AparteElementBase {
244
246
  set variant(v: string | undefined);
245
247
  /** Tokens in use before a turn of this page has reported any: a restored conversation's last usage. The next turn's usage replaces it. */
246
248
  set used(v: number);
247
- /** Dispatch `aparte-compact` on each turn that ends in `danger`, one request at a time. */
249
+ /** Deprecated, still works. Dispatch `aparte-compact` (with `auto: true`) on each turn that ends in `danger`, one request at a time. `@aparte/plugin-compaction` decides when to compact by itself now, so leave this off. */
248
250
  set autoCompact(v: boolean);
249
251
  /** Reflected BY the element: `ok`, `warn` or `danger`. Read-only. */
250
252
  set dataLevel(v: string | undefined);
251
253
  /** Reflected BY the element while it has nothing to show. Read-only. */
252
254
  set dataEmpty(v: boolean);
255
+ /** Reflected BY the element while this chat is being compacted (`running`) and after a compaction failed (`failed`). Read-only. */
256
+ set dataCompact(v: string | undefined);
253
257
  /** The level changed. Bubbles. */
254
258
  readonly contextThreshold: EventEmitter<AparteContextThresholdEventDetail>;
255
- /** Dispatched on `window` when `auto-compact` is set and a turn ends in `danger`: compact this target's transcript. One request at a time — the next turn in `danger` asks again once the plugin has answered — and only while the attribute is present. `@aparte/plugin-compaction` answers it; with no listener nothing happens. */
259
+ /** Dispatched on `window` when the deprecated `auto-compact` is set and a turn ends in `danger` (`auto: true`, with the `window` it measured against), and by Retry in the panel when no compactor is registered. One automatic request at a time — the next turn in `danger` asks again once the plugin has answered. `@aparte/plugin-compaction` answers it; with no listener nothing happens. */
256
260
  readonly compact: EventEmitter<AparteCompactEventDetail>;
257
261
  protected onContextThreshold(e: CustomEvent<AparteContextThresholdEventDetail>): void;
258
262
  protected onCompact(e: CustomEvent<AparteCompactEventDetail>): void;
259
263
  static ɵfac: i0.ɵɵFactoryDeclaration<AparteContextDirective, never>;
260
- static ɵdir: i0.ɵɵDirectiveDeclaration<AparteContextDirective, "aparte-context", never, { "window": { "alias": "window"; "required": false; }; "warn": { "alias": "warn"; "required": false; }; "danger": { "alias": "danger"; "required": false; }; "target": { "alias": "target"; "required": false; }; "variant": { "alias": "variant"; "required": false; }; "used": { "alias": "used"; "required": false; }; "autoCompact": { "alias": "autoCompact"; "required": false; }; "dataLevel": { "alias": "dataLevel"; "required": false; }; "dataEmpty": { "alias": "dataEmpty"; "required": false; }; }, { "contextThreshold": "contextThreshold"; "compact": "compact"; }, never, never, true, never>;
264
+ static ɵdir: i0.ɵɵDirectiveDeclaration<AparteContextDirective, "aparte-context", never, { "window": { "alias": "window"; "required": false; }; "warn": { "alias": "warn"; "required": false; }; "danger": { "alias": "danger"; "required": false; }; "target": { "alias": "target"; "required": false; }; "variant": { "alias": "variant"; "required": false; }; "used": { "alias": "used"; "required": false; }; "autoCompact": { "alias": "autoCompact"; "required": false; }; "dataLevel": { "alias": "dataLevel"; "required": false; }; "dataEmpty": { "alias": "dataEmpty"; "required": false; }; "dataCompact": { "alias": "dataCompact"; "required": false; }; }, { "contextThreshold": "contextThreshold"; "compact": "compact"; }, never, never, true, never>;
261
265
  static ngAcceptInputType_window: unknown;
262
266
  static ngAcceptInputType_warn: unknown;
263
267
  static ngAcceptInputType_danger: unknown;
@@ -275,6 +279,8 @@ export declare class AparteConversationListDirective extends AparteElementBase {
275
279
  set loading(v: boolean);
276
280
  /** Let the list finish the gesture: rename, pin, unpin, archive, unarchive and delete are performed on the conversation manager registered with `setConversationManager()`, instead of leaving you six listeners to write. The event still fires first, and `preventDefault()` on it takes the gesture back. Off by default, and selecting stays yours either way. */
277
281
  set manage(v: boolean);
282
+ /** Set the list of conversations to display. Triggers a re-render. */
283
+ set conversations(v: AparteConversationListItem[] | undefined);
278
284
  /** A row's title was activated; the host loads that conversation. */
279
285
  readonly conversationSelect: EventEmitter<AparteConversationSelectDetail>;
280
286
  /** A rename was committed with a new, non-empty title. Cancelable: `preventDefault()` takes the gesture back from `manage`. Angular: this Output emits the detail, so preventDefault() is not on it — bind the DOM event for that: (aparte-conversation-rename)="$event.preventDefault()". */
@@ -297,7 +303,7 @@ export declare class AparteConversationListDirective extends AparteElementBase {
297
303
  protected onConversationUnarchive(e: CustomEvent<AparteConversationArchiveDetail>): void;
298
304
  protected onConversationDelete(e: CustomEvent<AparteConversationDeleteDetail>): void;
299
305
  static ɵfac: i0.ɵɵFactoryDeclaration<AparteConversationListDirective, never>;
300
- static ɵdir: i0.ɵɵDirectiveDeclaration<AparteConversationListDirective, "aparte-conversation-list", never, { "activeId": { "alias": "activeId"; "required": false; }; "flat": { "alias": "flat"; "required": false; }; "loading": { "alias": "loading"; "required": false; }; "manage": { "alias": "manage"; "required": false; }; }, { "conversationSelect": "conversationSelect"; "conversationRename": "conversationRename"; "conversationPin": "conversationPin"; "conversationUnpin": "conversationUnpin"; "conversationArchive": "conversationArchive"; "conversationUnarchive": "conversationUnarchive"; "conversationDelete": "conversationDelete"; }, never, never, true, never>;
306
+ static ɵdir: i0.ɵɵDirectiveDeclaration<AparteConversationListDirective, "aparte-conversation-list", never, { "activeId": { "alias": "activeId"; "required": false; }; "flat": { "alias": "flat"; "required": false; }; "loading": { "alias": "loading"; "required": false; }; "manage": { "alias": "manage"; "required": false; }; "conversations": { "alias": "conversations"; "required": false; }; }, { "conversationSelect": "conversationSelect"; "conversationRename": "conversationRename"; "conversationPin": "conversationPin"; "conversationUnpin": "conversationUnpin"; "conversationArchive": "conversationArchive"; "conversationUnarchive": "conversationUnarchive"; "conversationDelete": "conversationDelete"; }, never, never, true, never>;
301
307
  static ngAcceptInputType_flat: unknown;
302
308
  static ngAcceptInputType_loading: unknown;
303
309
  static ngAcceptInputType_manage: unknown;
package/dist/index.d.ts CHANGED
@@ -5,10 +5,12 @@
5
5
  export { provideAparte, APARTE_CONFIG_TOKEN } from './lib/provide-aparte';
6
6
  export type { ProvideAparteOptions, ApartePluginLoader } from './lib/provide-aparte';
7
7
  export { AparteChatComponent } from './lib/aparte-chat.component';
8
+ export { AparteBubbleDirective } from './lib/aparte-bubble.directive';
9
+ export { AparteToolUIDirective, AparteSegmentUIDirective } from './lib/aparte-tool-ui.directive';
8
10
  export { AparteUiComponent } from './lib/aparte-ui.component';
9
11
  export type { AparteUiHandle, AparteUiProps } from './lib/aparte-ui.component';
10
12
  export * from './generated/element.directives';
11
13
  export { AparteAiService, APARTE_CLIENT_OPTIONS } from './lib/aparte-ai.service';
12
14
  export { ConversationManagerService } from './lib/conversation-manager.service';
13
- export type { AparteMessage, AparteSendEventDetail, AparteActionEventDetail, AparteSegment, AparteTextSegment, AparteCodeSegment, AparteThinkingSegment, AparteChatImperativeApi, } from '@aparte/core';
15
+ export type { AparteMessage, AparteSendEventDetail, AparteActionEventDetail, AparteSegment, AparteTextSegment, AparteCodeSegment, AparteThinkingSegment, AparteChatImperativeApi, AparteToolCallView, } from '@aparte/core';
14
16
  export { aparteGlobalConfig } from '@aparte/core';
@@ -0,0 +1,27 @@
1
+ import { TemplateRef } from '@angular/core';
2
+ import type { AparteMessage } from '@aparte/core';
3
+ import * as i0 from "@angular/core";
4
+ /**
5
+ * Marks the template `<aparte-chat>` draws each message with, in place of
6
+ * `<aparte-chat-bubble>`: write it as content of the chat, the message as `$implicit`.
7
+ *
8
+ * ```html
9
+ * <aparte-chat [messages]="messages">
10
+ * <ng-template aparteBubble let-message>
11
+ * <app-bubble [message]="message" />
12
+ * </ng-template>
13
+ * </aparte-chat>
14
+ * ```
15
+ *
16
+ * The same thing as passing a `TemplateRef` to `[bubbleTemplate]`, written where Angular
17
+ * developers write a per-item template. When both are given, `[bubbleTemplate]` wins.
18
+ * The built-in action bar (retry, edit, the version picker) belongs to the native bubble;
19
+ * a template of yours owns whatever it wires.
20
+ */
21
+ export declare class AparteBubbleDirective {
22
+ readonly template: TemplateRef<{
23
+ $implicit: AparteMessage;
24
+ }>;
25
+ static ɵfac: i0.ɵɵFactoryDeclaration<AparteBubbleDirective, never>;
26
+ static ɵdir: i0.ɵɵDirectiveDeclaration<AparteBubbleDirective, "ng-template[aparteBubble]", never, {}, {}, never, never, true, never>;
27
+ }
@@ -1,10 +1,11 @@
1
- import { ElementRef, OnDestroy, QueryList, AfterViewInit, TemplateRef } from '@angular/core';
1
+ import { AfterViewInit, ElementRef, OnDestroy, QueryList, TemplateRef } from '@angular/core';
2
2
  import { Observable } from 'rxjs';
3
3
  import type { AparteMessage, AparteSegment, AparteSendEventDetail, AparteConfig, AparteActionEventDetail, AparteChatImperativeApi } from '@aparte/core';
4
4
  import { isAwaitingReply } from '@aparte/core';
5
+ import { AparteBubbleDirective } from './aparte-bubble.directive';
5
6
  import * as i0 from "@angular/core";
6
7
  /**
7
- * AparteChatComponent — Angular 19 Wrapper
8
+ * AparteChatComponent — the Angular wrapper (19.2 to 21, zone or zoneless)
8
9
  *
9
10
  * Standalone component wrapping aparté Web Components with Angular Signals.
10
11
  * The streaming / branch-navigation / host-method orchestration (orphan-stream
@@ -15,25 +16,35 @@ import * as i0 from "@angular/core";
15
16
  */
16
17
  export declare class AparteChatComponent implements AfterViewInit, OnDestroy, AparteChatImperativeApi {
17
18
  constructor();
19
+ ngAfterViewInit(): void;
18
20
  private elementRef;
21
+ private readonly _injector;
22
+ private readonly _isBrowser;
19
23
  /**
20
24
  * Template bridge to core's shared `isAwaitingReply` (an Angular template can't
21
25
  * call an imported function). One rule for the four wrappers and core itself,
22
26
  * so "is this bubble in flight" can't drift between them.
23
27
  */
24
28
  readonly awaitingReply: typeof isAwaitingReply;
25
- /** Messages to display */
26
- set messagesInput(val: AparteMessage[]);
29
+ /** Messages to display. Two-way: `[(messages)]`, with `(messagesChange)` reporting the chat's own changes. */
30
+ readonly messagesInput: import("@angular/core").InputSignal<AparteMessage[]>;
31
+ /** Every list the host reported — read only by the render-cache rule below. */
32
+ private readonly _emitted;
33
+ /**
34
+ * Under `[(messages)]` each report comes back through the input — possibly an EARLIER one,
35
+ * after the chat appended without reporting (an optimistic append, or core's append right
36
+ * after a flush) — and an echo is not the parent's word: it keeps the local list. The host
37
+ * tells them apart (`takeEcho`), the rule React, Vue and Svelte apply, so a list the parent
38
+ * restores later (a reopened conversation, an undo) is applied. Before the host exists
39
+ * nothing was reported, so the input is taken as it is.
40
+ */
27
41
  readonly messages: import("@angular/core").WritableSignal<AparteMessage[]>;
28
42
  /** Input placeholder text */
29
- set placeholderInput(val: string);
30
- readonly placeholder: import("@angular/core").WritableSignal<string>;
43
+ readonly placeholder: import("@angular/core").InputSignal<string>;
31
44
  /** Whether the input is disabled */
32
- set disabledInput(val: boolean);
33
- readonly disabled: import("@angular/core").WritableSignal<boolean>;
45
+ readonly disabled: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
34
46
  /** When false, Shift+Enter submits and a bare Enter inserts a newline. */
35
- set submitOnEnterInput(val: boolean);
36
- readonly submitOnEnter: import("@angular/core").WritableSignal<boolean>;
47
+ readonly submitOnEnter: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
37
48
  /**
38
49
  * Opt in to the "centered composer when empty" layout: the composer sits
39
50
  * vertically centered with the `[slot='empty-state']` content above it while
@@ -41,21 +52,27 @@ export declare class AparteChatComponent implements AfterViewInit, OnDestroy, Ap
41
52
  * Off by default — additive (adds the `--auto-center` modifier + a
42
53
  * `data-aparte-empty` attribute the shipped `aparte.css` recipe keys off).
43
54
  */
44
- set centerWhenEmptyInput(val: boolean);
45
- readonly centerWhenEmpty: import("@angular/core").WritableSignal<boolean>;
55
+ readonly centerWhenEmpty: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
46
56
  /**
47
57
  * The conversation is on its way: the viewport draws two skeleton turns, the empty
48
58
  * state stays off and the composer is disabled until it lands. The conversation
49
59
  * controller sets this itself while it fetches through your adapter's `loadFull()`;
50
60
  * the input is for a wait of your own. Off by default.
51
61
  */
52
- set loadingInput(val: boolean);
53
- readonly loading: import("@angular/core").WritableSignal<boolean>;
62
+ readonly loading: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
54
63
  /** The controller's wait (a conversation fetched through the adapter). */
55
64
  readonly hostLoading: import("@angular/core").WritableSignal<boolean>;
56
65
  readonly waiting: import("@angular/core").Signal<boolean>;
57
66
  /** Bumped on every config change, so the two locale strings below follow a language switch under OnPush. */
58
67
  private readonly _localeTick;
68
+ /**
69
+ * True until the host is built — on the server always, and in the browser only while Angular
70
+ * hydrates the server's HTML: the host element is then the one the server sent, still carrying
71
+ * the attribute. A client-only render holds nothing, so a component projected into the empty
72
+ * state (built in an @if, after the host binding) talks to live elements in its own
73
+ * ngAfterViewInit, which runs before this component's.
74
+ */
75
+ protected readonly hydrating: import("@angular/core").WritableSignal<boolean>;
59
76
  private _unsubscribeLocale?;
60
77
  get loadingText(): string;
61
78
  /**
@@ -70,11 +87,9 @@ export declare class AparteChatComponent implements AfterViewInit, OnDestroy, Ap
70
87
  * selector on Angular alone. This input is that parity. Additive: the recipe's
71
88
  * own classes stay.
72
89
  */
73
- set containerClassInput(val: string);
74
- readonly containerClass: import("@angular/core").WritableSignal<string>;
90
+ readonly containerClass: import("@angular/core").InputSignalWithTransform<string, string | null | undefined>;
75
91
  /** Inline style for the same div — a string or a `{ prop: value }` map. */
76
- set containerStyleInput(val: string | Record<string, string> | null);
77
- readonly containerStyle: import("@angular/core").WritableSignal<string | Record<string, string>>;
92
+ readonly containerStyle: import("@angular/core").InputSignalWithTransform<string | Record<string, string> | null, string | Record<string, string> | null | undefined>;
78
93
  /**
79
94
  * Overlay the composer on the transcript (the ChatGPT anatomy): the scroll
80
95
  * surface spans the whole column, the scrollbar runs edge to edge, and the
@@ -83,8 +98,7 @@ export declare class AparteChatComponent implements AfterViewInit, OnDestroy, Ap
83
98
  * core's recipe and the viewport's measurement both key on the attribute
84
99
  * there. Read when the viewport mounts.
85
100
  */
86
- set overlayComposerInput(val: boolean);
87
- readonly overlayComposer: import("@angular/core").WritableSignal<boolean>;
101
+ readonly overlayComposer: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
88
102
  /**
89
103
  * Add the file picker + chips strip to the default composer shell. **Off by
90
104
  * default**, because the capability needs a host that consumes the files: an
@@ -94,8 +108,7 @@ export declare class AparteChatComponent implements AfterViewInit, OnDestroy, Ap
94
108
  * project a `[slot='composer']` (drop `<aparte-composer-add-attachment>` and
95
109
  * `<aparte-composer-attachments>` in it yourself).
96
110
  */
97
- set attachmentsInput(val: boolean);
98
- readonly attachments: import("@angular/core").WritableSignal<boolean>;
111
+ readonly attachments: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
99
112
  /**
100
113
  * Render the `<aparte-elicitation>` presenter inside the host — on by default, as
101
114
  * it is in `<aparte-chat>`: the built-in approval gate and `requestUserInput()`
@@ -103,46 +116,47 @@ export declare class AparteChatComponent implements AfterViewInit, OnDestroy, Ap
103
116
  * when you mount a presenter of your own (`setElicitationPresenter`) or place the
104
117
  * element yourself.
105
118
  */
106
- set elicitationInput(val: boolean);
107
- readonly elicitation: import("@angular/core").WritableSignal<boolean>;
108
- /** Whether the assistant is currently typing/streaming */
109
- set isTypingInput(val: boolean);
119
+ readonly elicitation: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
120
+ /** Whether the assistant is currently typing/streaming. The host turns it off on the first token. */
121
+ readonly isTypingInput: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
110
122
  readonly isTyping: import("@angular/core").WritableSignal<boolean>;
111
123
  /** Text to show in the typing status */
112
- set typingTextInput(val: string | undefined);
113
- readonly typingText: import("@angular/core").WritableSignal<string>;
124
+ readonly typingText: import("@angular/core").InputSignal<string | undefined>;
114
125
  /** The status line's word: the input, else the locale's, like the rest of the chat. */
115
126
  get typingLabel(): string;
116
- /** Duration in ms to freeze spacer recalculation after a conversation swap. */
117
- layoutTransitionMs: number;
127
+ /** Duration in ms to freeze spacer recalculation after a conversation swap. Read when the host is created. */
128
+ readonly layoutTransitionMs: import("@angular/core").InputSignalWithTransform<number, unknown>;
118
129
  /**
119
130
  * Active conversation id. When provided, the host attaches an
120
131
  * `AparteConversationController` that loads/persists messages via the
121
132
  * `AparteConversationManager` registered in `aparteGlobalConfig`. Setting a different id
122
133
  * mid-stream aborts the previous request; `null` deselects.
123
134
  */
124
- set conversationIdInput(val: string | null | undefined);
135
+ readonly conversationIdInput: import("@angular/core").InputSignal<string | null | undefined>;
125
136
  private _conversationId;
126
137
  /**
127
138
  * Instance {@link AparteConfig} for this chat. When set, aparté components
128
139
  * inside resolve THIS config instead of `aparteGlobalConfig`, so
129
140
  * several independently-configured chats can coexist on one page. Omit for the
130
- * global config. Read once in `ngAfterViewInit` when the host is created.
141
+ * global config. Read once, when the host is created after the first render.
131
142
  */
132
- config?: AparteConfig;
143
+ readonly config: import("@angular/core").InputSignal<AparteConfig | undefined>;
133
144
  /**
134
145
  * Render your OWN element per message in place of `<aparte-chat-bubble>`.
135
- * Opt-in — provide a `<ng-template let-message>` and pass its ref. Driven by
136
- * the reactive `messages()` signal, so it updates live during streaming.
137
- * The built-in action bar (retry/edit/branch) belongs to the native bubble;
138
- * a custom bubble owns whatever it wires.
146
+ * Opt-in — provide a `<ng-template let-message>` and pass its ref, or write the
147
+ * template as content with {@link AparteBubbleDirective} (`<ng-template aparteBubble
148
+ * let-message>`); this input wins when both are given. Driven by the reactive
149
+ * `messages()` signal, so it updates live during streaming. The built-in action bar
150
+ * (retry/edit/branch) belongs to the native bubble; a custom bubble owns whatever it wires.
139
151
  * @example
140
152
  * <aparte-chat [bubbleTemplate]="tpl" />
141
153
  * <ng-template #tpl let-message>{{ message.content }}</ng-template>
142
154
  */
143
- bubbleTemplate?: TemplateRef<{
155
+ readonly bubbleTemplate: import("@angular/core").InputSignal<TemplateRef<{
144
156
  $implicit: AparteMessage;
145
- }>;
157
+ }> | undefined>;
158
+ /** The `<ng-template aparteBubble>` written as content, if any. */
159
+ readonly bubbleDirective: import("@angular/core").Signal<AparteBubbleDirective | undefined>;
146
160
  /**
147
161
  * User submitted a message from the composer. It is **appended to the thread
148
162
  * automatically** (optimistic UI) before this fires — do NOT add it again in
@@ -173,13 +187,15 @@ export declare class AparteChatComponent implements AfterViewInit, OnDestroy, Ap
173
187
  readonly isStreaming: import("@angular/core").Signal<boolean>;
174
188
  /** rAF id used to coalesce rapid signal updates during streaming */
175
189
  private _syncRafId;
176
- /** The framework-agnostic chat-host orchestrator (created in ngAfterViewInit). */
190
+ /** The framework-agnostic chat-host orchestrator (created after the first render, in the browser). */
177
191
  private _host?;
178
192
  private _unbindHost?;
179
193
  /** QueryList.changes subscription — released in ngOnDestroy so SPA route
180
194
  * churn doesn't leak a live subscription per mount. */
181
195
  private _bubbleRefsSub?;
182
- ngAfterViewInit(): void;
196
+ /** Set by ngOnDestroy: an afterNextRender that lands after a destroy must not build a host. */
197
+ private _destroyed;
198
+ private _createHost;
183
199
  private readonly _onAction;
184
200
  ngOnDestroy(): void;
185
201
  /** Append a message optimistically — `{ historical: true }` for one restored from your own store. */
@@ -248,14 +264,5 @@ export declare class AparteChatComponent implements AfterViewInit, OnDestroy, Ap
248
264
  /** Handle aparte-send event from the composer Web Component. */
249
265
  onAparteSend(event: Event): void;
250
266
  static ɵfac: i0.ɵɵFactoryDeclaration<AparteChatComponent, never>;
251
- static ɵcmp: i0.ɵɵComponentDeclaration<AparteChatComponent, "aparte-chat", never, { "messagesInput": { "alias": "messages"; "required": false; }; "placeholderInput": { "alias": "placeholder"; "required": false; }; "disabledInput": { "alias": "disabled"; "required": false; }; "submitOnEnterInput": { "alias": "submitOnEnter"; "required": false; }; "centerWhenEmptyInput": { "alias": "centerWhenEmpty"; "required": false; }; "loadingInput": { "alias": "loading"; "required": false; }; "containerClassInput": { "alias": "containerClass"; "required": false; }; "containerStyleInput": { "alias": "containerStyle"; "required": false; }; "overlayComposerInput": { "alias": "overlayComposer"; "required": false; }; "attachmentsInput": { "alias": "attachments"; "required": false; }; "elicitationInput": { "alias": "elicitation"; "required": false; }; "isTypingInput": { "alias": "isTyping"; "required": false; }; "typingTextInput": { "alias": "typingText"; "required": false; }; "layoutTransitionMs": { "alias": "layoutTransitionMs"; "required": false; }; "conversationIdInput": { "alias": "conversationId"; "required": false; }; "config": { "alias": "config"; "required": false; }; "bubbleTemplate": { "alias": "bubbleTemplate"; "required": false; }; }, { "messageSent": "messageSent"; "action": "action"; "messagesChange": "messagesChange"; "messageAppended": "messageAppended"; "typingChange": "typingChange"; "conversationCreated": "conversationCreated"; }, never, ["[slot='empty-state']", "[slot='above-composer']", "[slot='toolbar']", "[slot='composer']"], true, never>;
252
- static ngAcceptInputType_disabledInput: unknown;
253
- static ngAcceptInputType_submitOnEnterInput: unknown;
254
- static ngAcceptInputType_centerWhenEmptyInput: unknown;
255
- static ngAcceptInputType_loadingInput: unknown;
256
- static ngAcceptInputType_overlayComposerInput: unknown;
257
- static ngAcceptInputType_attachmentsInput: unknown;
258
- static ngAcceptInputType_elicitationInput: unknown;
259
- static ngAcceptInputType_isTypingInput: unknown;
260
- static ngAcceptInputType_layoutTransitionMs: unknown;
267
+ static ɵcmp: i0.ɵɵComponentDeclaration<AparteChatComponent, "aparte-chat", never, { "messagesInput": { "alias": "messages"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "submitOnEnter": { "alias": "submitOnEnter"; "required": false; "isSignal": true; }; "centerWhenEmpty": { "alias": "centerWhenEmpty"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "containerClass": { "alias": "containerClass"; "required": false; "isSignal": true; }; "containerStyle": { "alias": "containerStyle"; "required": false; "isSignal": true; }; "overlayComposer": { "alias": "overlayComposer"; "required": false; "isSignal": true; }; "attachments": { "alias": "attachments"; "required": false; "isSignal": true; }; "elicitation": { "alias": "elicitation"; "required": false; "isSignal": true; }; "isTypingInput": { "alias": "isTyping"; "required": false; "isSignal": true; }; "typingText": { "alias": "typingText"; "required": false; "isSignal": true; }; "layoutTransitionMs": { "alias": "layoutTransitionMs"; "required": false; "isSignal": true; }; "conversationIdInput": { "alias": "conversationId"; "required": false; "isSignal": true; }; "config": { "alias": "config"; "required": false; "isSignal": true; }; "bubbleTemplate": { "alias": "bubbleTemplate"; "required": false; "isSignal": true; }; }, { "messageSent": "messageSent"; "action": "action"; "messagesChange": "messagesChange"; "messageAppended": "messageAppended"; "typingChange": "typingChange"; "conversationCreated": "conversationCreated"; }, ["bubbleDirective"], ["[slot='empty-state']", "[slot='above-composer']", "[slot='toolbar']", "[slot='composer']"], true, never>;
261
268
  }
@@ -0,0 +1,63 @@
1
+ import { type AparteConfig, type AparteSegmentBase, type AparteToolCallView, type AparteToolMatcher } from '@aparte/core';
2
+ import * as i0 from "@angular/core";
3
+ /**
4
+ * Draws a tool call with your own template, for as long as the template is in the page.
5
+ *
6
+ * ```html
7
+ * <ng-template aparteToolUI="get_weather" let-tool>
8
+ * <app-weather-card [tool]="tool" />
9
+ * </ng-template>
10
+ * ```
11
+ *
12
+ * `let-tool` is an `AparteToolCallView`; its `input` is the model's arguments, `unknown` until
13
+ * the card narrows it — give the card the view (`input.required<AparteToolCallView>()`) and type
14
+ * its arguments there.
15
+ *
16
+ * Every `get_weather` call is drawn with the template, the call's view as `$implicit`, and
17
+ * given back to core's row when the template leaves. The view is created where you declare
18
+ * the template, so the components in it inject what your app provides there. Underneath it is
19
+ * core's `overrideToolRenderer` + `defineMountedToolRenderer`; nothing is declared in a server
20
+ * render, and the cards are drawn in the browser.
21
+ */
22
+ export declare class AparteToolUIDirective {
23
+ /** The tool this draws: a name, a `RegExp`, or `'*'` for every tool. */
24
+ readonly name: import("@angular/core").InputSignal<AparteToolMatcher>;
25
+ /** The config to declare it on. Defaults to the global one. */
26
+ readonly config: import("@angular/core").InputSignal<AparteConfig | undefined>;
27
+ private readonly template;
28
+ private readonly container;
29
+ constructor();
30
+ /** Types `let-tool` in a strict template. */
31
+ static ngTemplateContextGuard(_dir: AparteToolUIDirective, _ctx: unknown): _ctx is {
32
+ $implicit: AparteToolCallView;
33
+ };
34
+ static ɵfac: i0.ɵɵFactoryDeclaration<AparteToolUIDirective, never>;
35
+ static ɵdir: i0.ɵɵDirectiveDeclaration<AparteToolUIDirective, "ng-template[aparteToolUI]", never, { "name": { "alias": "aparteToolUI"; "required": true; "isSignal": true; }; "config": { "alias": "aparteToolUIConfig"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
36
+ }
37
+ /**
38
+ * Draws a segment type with your own template — one your stream emits, or a built-in such as
39
+ * `code` — the segment as `$implicit`. Taking the last declaration off a built-in type gives it
40
+ * back to core's renderer.
41
+ *
42
+ * ```html
43
+ * <ng-template aparteSegmentUI="chart" let-segment>
44
+ * <app-chart [segment]="segment" />
45
+ * </ng-template>
46
+ * ```
47
+ *
48
+ * `let-segment` is the base segment with its own fields: the component it is handed to types it.
49
+ */
50
+ export declare class AparteSegmentUIDirective {
51
+ /** The segment `type` this draws. */
52
+ readonly type: import("@angular/core").InputSignal<string>;
53
+ /** The config to declare it on. Defaults to the global one. */
54
+ readonly config: import("@angular/core").InputSignal<AparteConfig | undefined>;
55
+ private readonly template;
56
+ private readonly container;
57
+ constructor();
58
+ static ngTemplateContextGuard(_dir: AparteSegmentUIDirective, _ctx: unknown): _ctx is {
59
+ $implicit: AparteSegmentBase & Record<string, unknown>;
60
+ };
61
+ static ɵfac: i0.ɵɵFactoryDeclaration<AparteSegmentUIDirective, never>;
62
+ static ɵdir: i0.ɵɵDirectiveDeclaration<AparteSegmentUIDirective, "ng-template[aparteSegmentUI]", never, { "type": { "alias": "aparteSegmentUI"; "required": true; "isSignal": true; }; "config": { "alias": "aparteSegmentUIConfig"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
63
+ }
@@ -76,6 +76,13 @@ export declare class AparteUiComponent implements AfterViewInit, OnChanges, OnDe
76
76
  readonly elementEvent: import("@angular/core").OutputEmitterRef<CustomEvent<any>>;
77
77
  /** The dynamically created Web Component element */
78
78
  private element;
79
+ /**
80
+ * On a server the element is not built: it would be serialized into the HTML without its
81
+ * props or listeners, hydration would keep it, and the browser would append its own beside
82
+ * it — two counters, two pickers. React, Vue and Svelte build theirs in an effect or onMount,
83
+ * which a server never runs.
84
+ */
85
+ private readonly isBrowser;
79
86
  /** Cleanup functions for event listeners */
80
87
  private eventCleanups;
81
88
  /** Joined key of the bound event set, so a fresh inline `[events]` array
@@ -30,9 +30,9 @@ export declare class ConversationManagerService implements OnDestroy {
30
30
  /** Archived conversations, newest first. */
31
31
  readonly archivedConversations: import("@angular/core").Signal<AparteConversation[]>;
32
32
  /** Currently active conversation id. */
33
- readonly activeId: import("@angular/core").WritableSignal<string>;
33
+ readonly activeId: import("@angular/core").WritableSignal<string | null>;
34
34
  /** The active conversation object (null when no conv is selected). */
35
- readonly activeConversation: import("@angular/core").Signal<AparteConversation>;
35
+ readonly activeConversation: import("@angular/core").Signal<AparteConversation | null>;
36
36
  /**
37
37
  * Initialise the service with a storage adapter.
38
38
  * Call once in your root component or app initialiser.
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@aparte/angular",
3
- "version": "0.24.0",
3
+ "version": "0.26.0",
4
4
  "homepage": "https://apartejs.dev/frameworks/angular/",
5
- "description": "Angular 19 wrapper for aparté — an ergonomic <aparte-chat> component plus services over the framework-agnostic web components.",
5
+ "description": "Angular wrapper for aparté (Angular 19.2 to 21, zone or zoneless) — an ergonomic <aparte-chat> component plus services over the framework-agnostic web components.",
6
6
  "type": "module",
7
7
  "sideEffects": false,
8
8
  "main": "./dist/fesm2022/aparte-angular.mjs",
@@ -28,25 +28,26 @@
28
28
  "node": ">=18"
29
29
  },
30
30
  "peerDependencies": {
31
- "@angular/common": "^19.2.0",
32
- "@angular/core": "^19.2.0",
33
- "@aparte/core": ">=0.24.0 <1.0.0",
31
+ "@angular/common": "^19.2.0 || ^20.0.0 || ^21.0.0",
32
+ "@angular/core": "^19.2.0 || ^20.0.0 || ^21.0.0",
33
+ "@aparte/core": ">=0.26.0 <1.0.0",
34
34
  "rxjs": "^7.8.0"
35
35
  },
36
36
  "devDependencies": {
37
- "@analogjs/vitest-angular": "^2.2.2",
38
37
  "@angular/common": "^19.2.0",
39
38
  "@angular/compiler": "^19.2.0",
40
39
  "@angular/compiler-cli": "^19.2.0",
41
40
  "@angular/core": "^19.2.0",
42
41
  "@angular/platform-browser": "^19.2.0",
42
+ "@angular/platform-server": "^19.2.0",
43
43
  "jsdom": "^22.1.0",
44
44
  "ng-packagr": "^19.2.0",
45
45
  "rxjs": "^7.8.2",
46
46
  "typescript": "^5.4.0",
47
47
  "vite": "^6.0.0",
48
48
  "zone.js": "~0.15.0",
49
- "@aparte/core": "0.24.0"
49
+ "@aparte/core": "0.26.0",
50
+ "@aparte/provider-scenario": "0.26.0"
50
51
  },
51
52
  "keywords": [
52
53
  "angular",
@@ -67,7 +68,7 @@
67
68
  "url": "https://github.com/apartejs/aparte/issues"
68
69
  },
69
70
  "scripts": {
70
- "build": "ng-packagr -p ng-package.json && node ../../../scripts/stamp-dist.mjs",
71
+ "build": "ng-packagr -p ng-package.json -c tsconfig.lib.json && node ../../../scripts/stamp-dist.mjs",
71
72
  "typecheck": "tsc -p tsconfig.json --noEmit",
72
73
  "typecheck:tests": "tsc -p tsconfig.spec.json --noEmit",
73
74
  "test": "vitest",