@aparte/angular 0.15.1 → 0.16.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.
@@ -2,7 +2,7 @@ import * as i0 from '@angular/core';
2
2
  import { InjectionToken, inject, Injectable, makeEnvironmentProviders, provideAppInitializer, DestroyRef, effect, ElementRef, signal, output, computed, numberAttribute, booleanAttribute, ViewChildren, ViewChild, Input, ChangeDetectionStrategy, CUSTOM_ELEMENTS_SCHEMA, Component, Renderer2, Directive, EventEmitter, HostListener, Output } from '@angular/core';
3
3
  import { AparteClient, aparteGlobalConfig, isAwaitingReply, uuid, AparteChatHost, APARTE_DEFAULT_UI_EVENTS, applyElementProps, AparteConversationManager } from '@aparte/core';
4
4
  export { aparteGlobalConfig } from '@aparte/core';
5
- import { NgTemplateOutlet } from '@angular/common';
5
+ import { DOCUMENT, NgTemplateOutlet } from '@angular/common';
6
6
 
7
7
  /**
8
8
  * Injection token for AparteClient configuration. Provided by `provideAparte()`;
@@ -104,17 +104,28 @@ async function loadPlugins(options) {
104
104
  pending.push(loadMarkdownPlugin(plugins.markdown));
105
105
  await Promise.all(pending);
106
106
  }
107
- /** Reflect the theme mode on the document root. */
108
- function applyThemeMode(mode, destroyRef) {
107
+ /**
108
+ * Reflect the theme mode on the document root.
109
+ *
110
+ * Angular's own document, not the global: under Universal the initializer runs on the
111
+ * server, where `document`/`window` do not exist and the injected DOCUMENT is Domino's.
112
+ * A media query needs a real window; without one the mode is applied once and left.
113
+ */
114
+ function applyThemeMode(mode, doc, destroyRef) {
109
115
  if (mode !== 'auto') {
110
- document.documentElement.setAttribute('data-aparte-theme', mode);
116
+ doc.documentElement.setAttribute('data-aparte-theme', mode);
111
117
  return;
112
118
  }
113
- const query = window.matchMedia('(prefers-color-scheme: dark)');
119
+ const win = doc.defaultView;
120
+ if (!win?.matchMedia) {
121
+ doc.documentElement.setAttribute('data-aparte-theme', 'light');
122
+ return;
123
+ }
124
+ const query = win.matchMedia('(prefers-color-scheme: dark)');
114
125
  const onChange = (e) => {
115
- document.documentElement.setAttribute('data-aparte-theme', e.matches ? 'dark' : 'light');
126
+ doc.documentElement.setAttribute('data-aparte-theme', e.matches ? 'dark' : 'light');
116
127
  };
117
- document.documentElement.setAttribute('data-aparte-theme', query.matches ? 'dark' : 'light');
128
+ doc.documentElement.setAttribute('data-aparte-theme', query.matches ? 'dark' : 'light');
118
129
  query.addEventListener('change', onChange);
119
130
  // Released with the environment injector, so repeated bootstraps (TestBed, a
120
131
  // multi-instance embed) don't stack listeners on the media query forever.
@@ -147,6 +158,8 @@ function provideAparte(options = {}) {
147
158
  { provide: APARTE_CLIENT_OPTIONS, useValue: options.clientOptions ?? {} },
148
159
  provideAppInitializer(async () => {
149
160
  const destroyRef = inject(DestroyRef);
161
+ // Before the first await, like `destroyRef`: inject() needs the context.
162
+ const doc = inject(DOCUMENT);
150
163
  // inject() only works before the first await — grab the service now,
151
164
  // connect at the end (after plugins) so the first send already sees
152
165
  // markdown/action providers. SSR-safe: no listener without a window.
@@ -164,7 +177,7 @@ function provideAparte(options = {}) {
164
177
  }
165
178
  await loadPlugins(options);
166
179
  if (options.themeMode) {
167
- applyThemeMode(options.themeMode, destroyRef);
180
+ applyThemeMode(options.themeMode, doc, destroyRef);
168
181
  }
169
182
  ai?.connect();
170
183
  }),
@@ -246,6 +259,15 @@ class AparteChatComponent {
246
259
  */
247
260
  set attachmentsInput(val) { this.attachments.set(val); }
248
261
  attachments = signal(false);
262
+ /**
263
+ * Render the `<aparte-elicitation>` presenter inside the host — on by default, as
264
+ * it is in `<aparte-chat>`: the built-in approval gate and `requestUserInput()`
265
+ * ask through it, and it renders nothing until something asks. Set it to `false`
266
+ * when you mount a presenter of your own (`setElicitationPresenter`) or place the
267
+ * element yourself.
268
+ */
269
+ set elicitationInput(val) { this.elicitation.set(val); }
270
+ elicitation = signal(true);
249
271
  /** Whether the assistant is currently typing/streaming */
250
272
  set isTypingInput(val) { this.isTyping.set(val); }
251
273
  isTyping = signal(false);
@@ -539,7 +561,7 @@ class AparteChatComponent {
539
561
  this.messageSent.emit(detail);
540
562
  }
541
563
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: AparteChatComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
542
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.25", type: AparteChatComponent, isStandalone: true, selector: "aparte-chat", inputs: { messagesInput: ["messages", "messagesInput"], placeholderInput: ["placeholder", "placeholderInput"], disabledInput: ["disabled", "disabledInput", booleanAttribute], submitOnEnterInput: ["submitOnEnter", "submitOnEnterInput", booleanAttribute], centerWhenEmptyInput: ["centerWhenEmpty", "centerWhenEmptyInput", booleanAttribute], attachmentsInput: ["attachments", "attachmentsInput", booleanAttribute], isTypingInput: ["isTyping", "isTypingInput", booleanAttribute], typingTextInput: ["typingText", "typingTextInput"], layoutTransitionMs: ["layoutTransitionMs", "layoutTransitionMs", numberAttribute], conversationIdInput: ["conversationId", "conversationIdInput"], config: "config", bubbleTemplate: "bubbleTemplate" }, outputs: { messageSent: "messageSent", action: "action", messagesChange: "messagesChange", messageAppended: "messageAppended", typingChange: "typingChange", conversationCreated: "conversationCreated" }, host: { attributes: { "framework-managed": "" } }, viewQueries: [{ propertyName: "viewportRef", first: true, predicate: ["viewport"], descendants: true }, { propertyName: "inputRef", first: true, predicate: ["input"], descendants: true }, { propertyName: "bubbleRefs", predicate: ["bubble"], descendants: true }], ngImport: i0, template: `
564
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.25", type: AparteChatComponent, isStandalone: true, selector: "aparte-chat", inputs: { messagesInput: ["messages", "messagesInput"], placeholderInput: ["placeholder", "placeholderInput"], disabledInput: ["disabled", "disabledInput", booleanAttribute], submitOnEnterInput: ["submitOnEnter", "submitOnEnterInput", booleanAttribute], centerWhenEmptyInput: ["centerWhenEmpty", "centerWhenEmptyInput", booleanAttribute], attachmentsInput: ["attachments", "attachmentsInput", booleanAttribute], elicitationInput: ["elicitation", "elicitationInput", booleanAttribute], isTypingInput: ["isTyping", "isTypingInput", booleanAttribute], typingTextInput: ["typingText", "typingTextInput"], layoutTransitionMs: ["layoutTransitionMs", "layoutTransitionMs", numberAttribute], conversationIdInput: ["conversationId", "conversationIdInput"], config: "config", bubbleTemplate: "bubbleTemplate" }, outputs: { messageSent: "messageSent", action: "action", messagesChange: "messagesChange", messageAppended: "messageAppended", typingChange: "typingChange", conversationCreated: "conversationCreated" }, host: { attributes: { "framework-managed": "" } }, viewQueries: [{ propertyName: "viewportRef", first: true, predicate: ["viewport"], descendants: true }, { propertyName: "inputRef", first: true, predicate: ["input"], descendants: true }, { propertyName: "bubbleRefs", predicate: ["bubble"], descendants: true }], ngImport: i0, template: `
543
565
  <div
544
566
  class="aparte-chat-container"
545
567
  [class.aparte-chat-container--auto-center]="centerWhenEmpty()"
@@ -563,6 +585,7 @@ class AparteChatComponent {
563
585
  #bubble
564
586
  [attr.message-id]="message.id"
565
587
  [attr.data-role]="message.role"
588
+ [attr.data-kind]="message.compaction ? 'compaction' : null"
566
589
  [attr.timestamp]="message.timestamp"
567
590
  [attr.content]="message.content"
568
591
  [attr.streaming]="awaitingReply(message) ? '' : null"
@@ -575,6 +598,12 @@ class AparteChatComponent {
575
598
  ></aparte-chat-status>
576
599
  </aparte-chat-viewport>
577
600
 
601
+ <!-- The presenter for a request to the human (approval gate, ask_user…).
602
+ Renders nothing until something asks; on by default as in <aparte-chat>. -->
603
+ @if (elicitation()) {
604
+ <aparte-elicitation></aparte-elicitation>
605
+ }
606
+
578
607
  <ng-content select="[slot='above-composer']"></ng-content>
579
608
 
580
609
  <aparte-composer
@@ -643,6 +672,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
643
672
  #bubble
644
673
  [attr.message-id]="message.id"
645
674
  [attr.data-role]="message.role"
675
+ [attr.data-kind]="message.compaction ? 'compaction' : null"
646
676
  [attr.timestamp]="message.timestamp"
647
677
  [attr.content]="message.content"
648
678
  [attr.streaming]="awaitingReply(message) ? '' : null"
@@ -655,6 +685,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
655
685
  ></aparte-chat-status>
656
686
  </aparte-chat-viewport>
657
687
 
688
+ <!-- The presenter for a request to the human (approval gate, ask_user…).
689
+ Renders nothing until something asks; on by default as in <aparte-chat>. -->
690
+ @if (elicitation()) {
691
+ <aparte-elicitation></aparte-elicitation>
692
+ }
693
+
658
694
  <ng-content select="[slot='above-composer']"></ng-content>
659
695
 
660
696
  <aparte-composer
@@ -714,6 +750,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
714
750
  }], attachmentsInput: [{
715
751
  type: Input,
716
752
  args: [{ alias: 'attachments', transform: booleanAttribute }]
753
+ }], elicitationInput: [{
754
+ type: Input,
755
+ args: [{ alias: 'elicitation', transform: booleanAttribute }]
717
756
  }], isTypingInput: [{
718
757
  type: Input,
719
758
  args: [{ alias: 'isTyping', transform: booleanAttribute }]
@@ -980,6 +1019,8 @@ class AparteChatBubbleDirective extends AparteElementBase {
980
1019
  edit = new EventEmitter();
981
1020
  /** The `‹1/2›` picker moved between sibling versions. */
982
1021
  branchNavigate = new EventEmitter();
1022
+ /** A link in the message body is about to be followed. Cancelable: `preventDefault()` keeps the browser from navigating, so a host can route the link itself. */
1023
+ linkClick = new EventEmitter();
983
1024
  onAttachmentPreview(e) { this.attachmentPreview.emit(e.detail); }
984
1025
  onAction(e) { this.action.emit(e.detail); }
985
1026
  onRetry(e) { this.retry.emit(e.detail); }
@@ -987,8 +1028,9 @@ class AparteChatBubbleDirective extends AparteElementBase {
987
1028
  onMessageInfo(e) { this.messageInfo.emit(e.detail); }
988
1029
  onEdit(e) { this.edit.emit(e.detail); }
989
1030
  onBranchNavigate(e) { this.branchNavigate.emit(e.detail); }
1031
+ onLinkClick(e) { this.linkClick.emit(e.detail); }
990
1032
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: AparteChatBubbleDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive });
991
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "19.2.25", type: AparteChatBubbleDirective, isStandalone: true, selector: "aparte-chat-bubble", inputs: { messageRole: "messageRole", content: "content", timestamp: "timestamp", messageId: "messageId", streaming: ["streaming", "streaming", booleanAttribute], name: "name" }, outputs: { attachmentPreview: "attachmentPreview", action: "action", retry: "retry", feedback: "feedback", messageInfo: "messageInfo", edit: "edit", branchNavigate: "branchNavigate" }, host: { listeners: { "aparte-attachment-preview": "onAttachmentPreview($event)", "aparte-action": "onAction($event)", "aparte-retry": "onRetry($event)", "aparte-feedback": "onFeedback($event)", "aparte-message-info": "onMessageInfo($event)", "aparte-edit": "onEdit($event)", "aparte-branch-navigate": "onBranchNavigate($event)" } }, usesInheritance: true, ngImport: i0 });
1033
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "19.2.25", type: AparteChatBubbleDirective, isStandalone: true, selector: "aparte-chat-bubble", inputs: { messageRole: "messageRole", content: "content", timestamp: "timestamp", messageId: "messageId", streaming: ["streaming", "streaming", booleanAttribute], name: "name" }, outputs: { attachmentPreview: "attachmentPreview", action: "action", retry: "retry", feedback: "feedback", messageInfo: "messageInfo", edit: "edit", branchNavigate: "branchNavigate", linkClick: "linkClick" }, host: { listeners: { "aparte-attachment-preview": "onAttachmentPreview($event)", "aparte-action": "onAction($event)", "aparte-retry": "onRetry($event)", "aparte-feedback": "onFeedback($event)", "aparte-message-info": "onMessageInfo($event)", "aparte-edit": "onEdit($event)", "aparte-branch-navigate": "onBranchNavigate($event)", "aparte-link-click": "onLinkClick($event)" } }, usesInheritance: true, ngImport: i0 });
992
1034
  }
993
1035
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: AparteChatBubbleDirective, decorators: [{
994
1036
  type: Directive,
@@ -1020,6 +1062,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
1020
1062
  type: Output
1021
1063
  }], branchNavigate: [{
1022
1064
  type: Output
1065
+ }], linkClick: [{
1066
+ type: Output
1023
1067
  }], onAttachmentPreview: [{
1024
1068
  type: HostListener,
1025
1069
  args: ['aparte-attachment-preview', ['$event']]
@@ -1041,6 +1085,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
1041
1085
  }], onBranchNavigate: [{
1042
1086
  type: HostListener,
1043
1087
  args: ['aparte-branch-navigate', ['$event']]
1088
+ }], onLinkClick: [{
1089
+ type: HostListener,
1090
+ args: ['aparte-link-click', ['$event']]
1044
1091
  }] } });
1045
1092
  /** `<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. */
1046
1093
  class AparteChatStatusDirective extends AparteElementBase {
@@ -1324,6 +1371,8 @@ class AparteContextDirective extends AparteElementBase {
1324
1371
  set danger(v) { this.write('danger', v); }
1325
1372
  /** The id of the `<aparte-chat>` to watch, when the element is not under it. */
1326
1373
  set target(v) { this.write('target', v); }
1374
+ /** `bar` (default) or `ring`: a progress bar with the reading beside it, or a ring with the percentage. */
1375
+ set variant(v) { this.write('variant', v); }
1327
1376
  /** Dispatch `aparte-compact` on reaching `danger` (once per crossing). */
1328
1377
  set autoCompact(v) { this.write('auto-compact', v); }
1329
1378
  /** Reflected BY the element: `ok`, `warn` or `danger`. Read-only. */
@@ -1332,9 +1381,12 @@ class AparteContextDirective extends AparteElementBase {
1332
1381
  set dataEmpty(v) { this.write('data-empty', v); }
1333
1382
  /** The level changed. Bubbles. */
1334
1383
  contextThreshold = new EventEmitter();
1384
+ /** Dispatched on `window` when `auto-compact` is set and the gauge first reaches `danger`: compact this target's transcript. Once per crossing, and only while the attribute is present. `@aparte/plugin-compaction` answers it; with no listener nothing happens. */
1385
+ compact = new EventEmitter();
1335
1386
  onContextThreshold(e) { this.contextThreshold.emit(e.detail); }
1387
+ onCompact(e) { this.compact.emit(e.detail); }
1336
1388
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: AparteContextDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive });
1337
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "19.2.25", type: AparteContextDirective, isStandalone: true, selector: "aparte-context", inputs: { window: ["window", "window", numberAttribute], warn: ["warn", "warn", numberAttribute], danger: ["danger", "danger", numberAttribute], target: "target", autoCompact: ["autoCompact", "autoCompact", booleanAttribute], dataLevel: "dataLevel", dataEmpty: ["dataEmpty", "dataEmpty", booleanAttribute] }, outputs: { contextThreshold: "contextThreshold" }, host: { listeners: { "aparte-context-threshold": "onContextThreshold($event)" } }, usesInheritance: true, ngImport: i0 });
1389
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "19.2.25", type: AparteContextDirective, isStandalone: true, selector: "aparte-context", inputs: { window: ["window", "window", numberAttribute], warn: ["warn", "warn", numberAttribute], danger: ["danger", "danger", numberAttribute], target: "target", variant: "variant", autoCompact: ["autoCompact", "autoCompact", booleanAttribute], dataLevel: "dataLevel", dataEmpty: ["dataEmpty", "dataEmpty", booleanAttribute] }, outputs: { contextThreshold: "contextThreshold", compact: "compact" }, host: { listeners: { "aparte-context-threshold": "onContextThreshold($event)", "aparte-compact": "onCompact($event)" } }, usesInheritance: true, ngImport: i0 });
1338
1390
  }
1339
1391
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: AparteContextDirective, decorators: [{
1340
1392
  type: Directive,
@@ -1350,6 +1402,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
1350
1402
  args: [{ transform: numberAttribute }]
1351
1403
  }], target: [{
1352
1404
  type: Input
1405
+ }], variant: [{
1406
+ type: Input
1353
1407
  }], autoCompact: [{
1354
1408
  type: Input,
1355
1409
  args: [{ transform: booleanAttribute }]
@@ -1360,54 +1414,88 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
1360
1414
  args: [{ transform: booleanAttribute }]
1361
1415
  }], contextThreshold: [{
1362
1416
  type: Output
1417
+ }], compact: [{
1418
+ type: Output
1363
1419
  }], onContextThreshold: [{
1364
1420
  type: HostListener,
1365
1421
  args: ['aparte-context-threshold', ['$event']]
1422
+ }], onCompact: [{
1423
+ type: HostListener,
1424
+ args: ['aparte-compact', ['$event']]
1366
1425
  }] } });
1367
- /** `<aparte-conversation-list>` — Conversation-history sidebar — a framework-agnostic web component. The host sets the `conversations` JS property and the `active-id` attribute; this renders the list and fires the user's intent, never acting on it itself. */
1426
+ /** `<aparte-conversation-list>` — Conversation-history sidebar — a framework-agnostic web component. The host sets the `conversations` JS property and the `active-id` attribute; this renders the rows and fires the user's intent, never acting on it itself. */
1368
1427
  class AparteConversationListDirective extends AparteElementBase {
1369
1428
  /** The id of the conversation to render as selected. */
1370
1429
  set activeId(v) { this.write('active-id', v); }
1371
- /** A row was activated; the host loads that conversation. */
1372
- selectConversation = new EventEmitter();
1373
- /** The delete action was pressed. Nothing is removed here. */
1374
- deleteConversation = new EventEmitter();
1375
- /** The archive action was pressed on a live conversation. */
1376
- archiveConversation = new EventEmitter();
1377
- /** The same action on an already-archived one; same detail shape, opposite intent. */
1430
+ /** Render the rows flat, in host order, with no date headings. */
1431
+ set noGroups(v) { this.write('no-groups', v); }
1432
+ /** The same item on a pinned row; same detail shape, opposite intent. */
1433
+ unpinConversation = new EventEmitter();
1434
+ /** The pin item was chosen on an unpinned row. */
1435
+ pinConversation = new EventEmitter();
1436
+ /** The same item on an already-archived one; same detail shape, opposite intent. */
1378
1437
  unarchiveConversation = new EventEmitter();
1379
- onSelectConversation(e) { this.selectConversation.emit(e.detail); }
1380
- onDeleteConversation(e) { this.deleteConversation.emit(e.detail); }
1381
- onArchiveConversation(e) { this.archiveConversation.emit(e.detail); }
1438
+ /** The archive item was chosen on a live conversation. */
1439
+ archiveConversation = new EventEmitter();
1440
+ /** The delete was confirmed. Nothing is removed here. */
1441
+ deleteConversation = new EventEmitter();
1442
+ /** A rename was committed with a new, non-empty title. Nothing is renamed here. */
1443
+ renameConversation = new EventEmitter();
1444
+ /** A row's title was activated; the host loads that conversation. */
1445
+ selectConversation = new EventEmitter();
1446
+ onUnpinConversation(e) { this.unpinConversation.emit(e.detail); }
1447
+ onPinConversation(e) { this.pinConversation.emit(e.detail); }
1382
1448
  onUnarchiveConversation(e) { this.unarchiveConversation.emit(e.detail); }
1449
+ onArchiveConversation(e) { this.archiveConversation.emit(e.detail); }
1450
+ onDeleteConversation(e) { this.deleteConversation.emit(e.detail); }
1451
+ onRenameConversation(e) { this.renameConversation.emit(e.detail); }
1452
+ onSelectConversation(e) { this.selectConversation.emit(e.detail); }
1383
1453
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: AparteConversationListDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive });
1384
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.25", type: AparteConversationListDirective, isStandalone: true, selector: "aparte-conversation-list", inputs: { activeId: "activeId" }, outputs: { selectConversation: "selectConversation", deleteConversation: "deleteConversation", archiveConversation: "archiveConversation", unarchiveConversation: "unarchiveConversation" }, host: { listeners: { "aparte-select-conversation": "onSelectConversation($event)", "aparte-delete-conversation": "onDeleteConversation($event)", "aparte-archive-conversation": "onArchiveConversation($event)", "aparte-unarchive-conversation": "onUnarchiveConversation($event)" } }, usesInheritance: true, ngImport: i0 });
1454
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "19.2.25", type: AparteConversationListDirective, isStandalone: true, selector: "aparte-conversation-list", inputs: { activeId: "activeId", noGroups: ["noGroups", "noGroups", booleanAttribute] }, outputs: { unpinConversation: "unpinConversation", pinConversation: "pinConversation", unarchiveConversation: "unarchiveConversation", archiveConversation: "archiveConversation", deleteConversation: "deleteConversation", renameConversation: "renameConversation", selectConversation: "selectConversation" }, host: { listeners: { "aparte-unpin-conversation": "onUnpinConversation($event)", "aparte-pin-conversation": "onPinConversation($event)", "aparte-unarchive-conversation": "onUnarchiveConversation($event)", "aparte-archive-conversation": "onArchiveConversation($event)", "aparte-delete-conversation": "onDeleteConversation($event)", "aparte-rename-conversation": "onRenameConversation($event)", "aparte-select-conversation": "onSelectConversation($event)" } }, usesInheritance: true, ngImport: i0 });
1385
1455
  }
1386
1456
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: AparteConversationListDirective, decorators: [{
1387
1457
  type: Directive,
1388
1458
  args: [{ selector: 'aparte-conversation-list', standalone: true }]
1389
1459
  }], propDecorators: { activeId: [{
1390
1460
  type: Input
1391
- }], selectConversation: [{
1461
+ }], noGroups: [{
1462
+ type: Input,
1463
+ args: [{ transform: booleanAttribute }]
1464
+ }], unpinConversation: [{
1392
1465
  type: Output
1393
- }], deleteConversation: [{
1466
+ }], pinConversation: [{
1467
+ type: Output
1468
+ }], unarchiveConversation: [{
1394
1469
  type: Output
1395
1470
  }], archiveConversation: [{
1396
1471
  type: Output
1397
- }], unarchiveConversation: [{
1472
+ }], deleteConversation: [{
1398
1473
  type: Output
1399
- }], onSelectConversation: [{
1474
+ }], renameConversation: [{
1475
+ type: Output
1476
+ }], selectConversation: [{
1477
+ type: Output
1478
+ }], onUnpinConversation: [{
1400
1479
  type: HostListener,
1401
- args: ['aparte-select-conversation', ['$event']]
1402
- }], onDeleteConversation: [{
1480
+ args: ['aparte-unpin-conversation', ['$event']]
1481
+ }], onPinConversation: [{
1403
1482
  type: HostListener,
1404
- args: ['aparte-delete-conversation', ['$event']]
1483
+ args: ['aparte-pin-conversation', ['$event']]
1484
+ }], onUnarchiveConversation: [{
1485
+ type: HostListener,
1486
+ args: ['aparte-unarchive-conversation', ['$event']]
1405
1487
  }], onArchiveConversation: [{
1406
1488
  type: HostListener,
1407
1489
  args: ['aparte-archive-conversation', ['$event']]
1408
- }], onUnarchiveConversation: [{
1490
+ }], onDeleteConversation: [{
1409
1491
  type: HostListener,
1410
- args: ['aparte-unarchive-conversation', ['$event']]
1492
+ args: ['aparte-delete-conversation', ['$event']]
1493
+ }], onRenameConversation: [{
1494
+ type: HostListener,
1495
+ args: ['aparte-rename-conversation', ['$event']]
1496
+ }], onSelectConversation: [{
1497
+ type: HostListener,
1498
+ args: ['aparte-select-conversation', ['$event']]
1411
1499
  }] } });
1412
1500
  /** `<aparte-elicitation>` — The default presenter for a request to the human. It renders nothing itself: it registers as the presenter for the config governing its subtree, and mounts a panel inside the nearest `<aparte-composer>` when something asks — a tool handler calling `requestUserInput`, or core's own approval gate. */
1413
1501
  /**
@@ -1514,6 +1602,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
1514
1602
  type: Input,
1515
1603
  args: [{ transform: numberAttribute }]
1516
1604
  }] } });
1605
+ /** `<aparte-scroll-rail>` — A rail of ticks beside the transcript — one per turn — that shows where you are in a long conversation and jumps back to any message on a click. */
1606
+ class AparteScrollRailDirective extends AparteElementBase {
1607
+ /** The id of the `<aparte-chat>` to follow, when the element is not inside it. */
1608
+ set target(v) { this.write('target', v); }
1609
+ /** `user` (default): one tick per user turn. `message`: one per message. */
1610
+ set every(v) { this.write('every', v); }
1611
+ /** Reflected BY the element while it has fewer than two ticks. Read-only. */
1612
+ set dataEmpty(v) { this.write('data-empty', v); }
1613
+ /** A tick was activated. Bubbles, cancelable: `preventDefault()` leaves the transcript where it is. */
1614
+ scrollRailJump = new EventEmitter();
1615
+ onScrollRailJump(e) { this.scrollRailJump.emit(e.detail); }
1616
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: AparteScrollRailDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive });
1617
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "19.2.25", type: AparteScrollRailDirective, isStandalone: true, selector: "aparte-scroll-rail", inputs: { target: "target", every: "every", dataEmpty: ["dataEmpty", "dataEmpty", booleanAttribute] }, outputs: { scrollRailJump: "scrollRailJump" }, host: { listeners: { "aparte-scroll-rail-jump": "onScrollRailJump($event)" } }, usesInheritance: true, ngImport: i0 });
1618
+ }
1619
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: AparteScrollRailDirective, decorators: [{
1620
+ type: Directive,
1621
+ args: [{ selector: 'aparte-scroll-rail', standalone: true }]
1622
+ }], propDecorators: { target: [{
1623
+ type: Input
1624
+ }], every: [{
1625
+ type: Input
1626
+ }], dataEmpty: [{
1627
+ type: Input,
1628
+ args: [{ transform: booleanAttribute }]
1629
+ }], scrollRailJump: [{
1630
+ type: Output
1631
+ }], onScrollRailJump: [{
1632
+ type: HostListener,
1633
+ args: ['aparte-scroll-rail-jump', ['$event']]
1634
+ }] } });
1517
1635
  /** `<aparte-select>` — Dropdown select for aparté — a vanilla web component with optional grouping, a search filter and a keyboard-driven listbox. */
1518
1636
  class AparteSelectDirective extends AparteElementBase {
1519
1637
  /** The selected option's value. */
@@ -1575,6 +1693,94 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
1575
1693
  type: HostListener,
1576
1694
  args: ['aparte-select-change', ['$event']]
1577
1695
  }] } });
1696
+ /** `<aparte-sidebar>` — The column beside the chat — conversations, a new-chat button, a search field, an account row — as an element, because it has behaviour: it collapses, it becomes a drawer on a narrow window, and its search field filters the conversation list. */
1697
+ class AparteSidebarDirective extends AparteElementBase {
1698
+ /** Hidden (in the flow) or closed (as a drawer). Reflected; set it to start closed. */
1699
+ set collapsed(v) { this.write('collapsed', v); }
1700
+ /** The window width under which the sidebar becomes a drawer: a length (default `48rem`), or `none` for a column that never does. */
1701
+ set breakpoint(v) { this.write('breakpoint', v); }
1702
+ /** Reflected BY the element while the window is under 48rem. Read-only. */
1703
+ set dataDrawer(v) { this.write('data-drawer', v); }
1704
+ /** The sidebar opened or closed, by a toggle, by Escape, by a click on the scrim or by `collapsed` being set. Bubbles. */
1705
+ sidebarToggle = new EventEmitter();
1706
+ onSidebarToggle(e) { this.sidebarToggle.emit(e.detail); }
1707
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: AparteSidebarDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive });
1708
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "19.2.25", type: AparteSidebarDirective, isStandalone: true, selector: "aparte-sidebar", inputs: { collapsed: ["collapsed", "collapsed", booleanAttribute], breakpoint: "breakpoint", dataDrawer: ["dataDrawer", "dataDrawer", booleanAttribute] }, outputs: { sidebarToggle: "sidebarToggle" }, host: { listeners: { "aparte-sidebar-toggle": "onSidebarToggle($event)" } }, usesInheritance: true, ngImport: i0 });
1709
+ }
1710
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: AparteSidebarDirective, decorators: [{
1711
+ type: Directive,
1712
+ args: [{ selector: 'aparte-sidebar', standalone: true }]
1713
+ }], propDecorators: { collapsed: [{
1714
+ type: Input,
1715
+ args: [{ transform: booleanAttribute }]
1716
+ }], breakpoint: [{
1717
+ type: Input
1718
+ }], dataDrawer: [{
1719
+ type: Input,
1720
+ args: [{ transform: booleanAttribute }]
1721
+ }], sidebarToggle: [{
1722
+ type: Output
1723
+ }], onSidebarToggle: [{
1724
+ type: HostListener,
1725
+ args: ['aparte-sidebar-toggle', ['$event']]
1726
+ }] } });
1727
+ /** `<aparte-split>` — Two panes and a draggable seam — a chat beside a preview, an editor, an artifact card. */
1728
+ class AparteSplitDirective extends AparteElementBase {
1729
+ /** The primary pane's size, as a percentage of the container. Reflected on commit, never per drag frame. */
1730
+ set position(v) { this.write('position', v); }
1731
+ /** `horizontal` (panes side by side, the default) or `vertical` (stacked). Names the CONTAINER's axis. */
1732
+ set orientation(v) { this.write('orientation', v); }
1733
+ /** `start` (default) or `end`: which pane `position` sizes. */
1734
+ set primary(v) { this.write('primary', v); }
1735
+ /** The primary pane folded to its minimum. Enter on the seam toggles it; a second Enter restores the size it had. */
1736
+ set collapsed(v) { this.write('collapsed', v); }
1737
+ /** Below this width the split shows one pane at a time. A length (default `48rem`), or `none` to never stack. */
1738
+ set breakpoint(v) { this.write('breakpoint', v); }
1739
+ /** `start` (default) or `end`: which pane is shown while stacked. */
1740
+ set pane(v) { this.write('pane', v); }
1741
+ /** No drag, no keys, no tab stop; the seam stays drawn. */
1742
+ set disabled(v) { this.write('disabled', v); }
1743
+ /** The seam's accessible name. Defaults to the locale's `splitHandleLabel`. */
1744
+ set label(v) { this.write('label', v); }
1745
+ /** Written BY the element while one pane is shown. Read-only; style against it. */
1746
+ set dataStacked(v) { this.write('data-stacked', v); }
1747
+ /** The position settled: after a drag, a key, a double-click or a property set. Never during a drag — persist from here. */
1748
+ splitResize = new EventEmitter();
1749
+ onSplitResize(e) { this.splitResize.emit(e.detail); }
1750
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: AparteSplitDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive });
1751
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "19.2.25", type: AparteSplitDirective, isStandalone: true, selector: "aparte-split", inputs: { position: ["position", "position", numberAttribute], orientation: "orientation", primary: "primary", collapsed: ["collapsed", "collapsed", booleanAttribute], breakpoint: "breakpoint", pane: "pane", disabled: ["disabled", "disabled", booleanAttribute], label: "label", dataStacked: ["dataStacked", "dataStacked", booleanAttribute] }, outputs: { splitResize: "splitResize" }, host: { listeners: { "aparte-split-resize": "onSplitResize($event)" } }, usesInheritance: true, ngImport: i0 });
1752
+ }
1753
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: AparteSplitDirective, decorators: [{
1754
+ type: Directive,
1755
+ args: [{ selector: 'aparte-split', standalone: true }]
1756
+ }], propDecorators: { position: [{
1757
+ type: Input,
1758
+ args: [{ transform: numberAttribute }]
1759
+ }], orientation: [{
1760
+ type: Input
1761
+ }], primary: [{
1762
+ type: Input
1763
+ }], collapsed: [{
1764
+ type: Input,
1765
+ args: [{ transform: booleanAttribute }]
1766
+ }], breakpoint: [{
1767
+ type: Input
1768
+ }], pane: [{
1769
+ type: Input
1770
+ }], disabled: [{
1771
+ type: Input,
1772
+ args: [{ transform: booleanAttribute }]
1773
+ }], label: [{
1774
+ type: Input
1775
+ }], dataStacked: [{
1776
+ type: Input,
1777
+ args: [{ transform: booleanAttribute }]
1778
+ }], splitResize: [{
1779
+ type: Output
1780
+ }], onSplitResize: [{
1781
+ type: HostListener,
1782
+ args: ['aparte-split-resize', ['$event']]
1783
+ }] } });
1578
1784
  /** `<aparte-suggestions>` — Prompt starters — a row of suggested prompts a reader clicks instead of typing. */
1579
1785
  class AparteSuggestionsDirective extends AparteElementBase {
1580
1786
  /** The starters, as a JSON array: strings, or `{ "label", "prompt" }` objects when the visible label and the sent text differ. The `suggestions` PROPERTY takes the same shape without the JSON. */
@@ -1640,7 +1846,10 @@ const APARTE_ELEMENT_DIRECTIVES = [
1640
1846
  AparteOptgroupDirective,
1641
1847
  AparteOptionDirective,
1642
1848
  AparteProgressSpinnerDirective,
1849
+ AparteScrollRailDirective,
1643
1850
  AparteSelectDirective,
1851
+ AparteSidebarDirective,
1852
+ AparteSplitDirective,
1644
1853
  AparteSuggestionsDirective,
1645
1854
  ];
1646
1855
 
@@ -1754,6 +1963,21 @@ class ConversationManagerService {
1754
1963
  this._assertInit();
1755
1964
  return this._manager.unarchive(id);
1756
1965
  }
1966
+ /** Pin a conversation to the top of the list. */
1967
+ async pin(id) {
1968
+ this._assertInit();
1969
+ return this._manager.pin(id);
1970
+ }
1971
+ /** Unpin a conversation. */
1972
+ async unpin(id) {
1973
+ this._assertInit();
1974
+ return this._manager.unpin(id);
1975
+ }
1976
+ /** Rename a conversation. */
1977
+ async updateTitle(id, title) {
1978
+ this._assertInit();
1979
+ return this._manager.updateTitle(id, title);
1980
+ }
1757
1981
  // ─── Private ─────────────────────────────────────────────────────────────
1758
1982
  _assertInit() {
1759
1983
  if (!this._manager) {
@@ -1778,5 +2002,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
1778
2002
  * Generated bundle index. Do not edit.
1779
2003
  */
1780
2004
 
1781
- export { APARTE_CLIENT_OPTIONS, APARTE_CONFIG_TOKEN, APARTE_ELEMENT_DIRECTIVES, AparteAiService, AparteChatBubbleDirective, AparteChatComponent, AparteChatStatusDirective, AparteChatViewportDirective, AparteComposerActionDirective, AparteComposerAddAttachmentDirective, AparteComposerAttachmentsDirective, AparteComposerCancelDirective, AparteComposerDirective, AparteComposerInputDirective, AparteComposerSendDirective, AparteComposerToolbarDirective, AparteConversationListDirective, AparteElicitationDirective, AparteOptgroupDirective, AparteOptionDirective, AparteProgressSpinnerDirective, AparteSelectDirective, AparteUiComponent, ConversationManagerService, provideAparte };
2005
+ export { APARTE_CLIENT_OPTIONS, APARTE_CONFIG_TOKEN, APARTE_ELEMENT_DIRECTIVES, AparteAiService, AparteChatBubbleDirective, AparteChatComponent, AparteChatStatusDirective, AparteChatViewportDirective, AparteComposerActionDirective, AparteComposerAddAttachmentDirective, AparteComposerAttachmentsDirective, AparteComposerCancelDirective, AparteComposerDirective, AparteComposerInputDirective, AparteComposerSendDirective, AparteComposerToolbarDirective, AparteContextDirective, AparteConversationListDirective, AparteElicitationDirective, AparteIconDirective, AparteOptgroupDirective, AparteOptionDirective, AparteProgressSpinnerDirective, AparteScrollRailDirective, AparteSelectDirective, AparteSidebarDirective, AparteSplitDirective, AparteSuggestionsDirective, AparteUiComponent, ConversationManagerService, provideAparte };
1782
2006
  //# sourceMappingURL=aparte-angular.mjs.map