@tylertech/forge-ai 0.14.0 → 0.15.1

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.
Files changed (47) hide show
  1. package/custom-elements.json +417 -153
  2. package/dist/ai-agent-selector/ai-agent-selector.mjs +1 -1
  3. package/dist/ai-chat-header/ai-chat-header.d.ts +5 -0
  4. package/dist/ai-chat-header/ai-chat-header.mjs +9 -3
  5. package/dist/ai-chat-header/ai-chat-header.scss.mjs +1 -1
  6. package/dist/ai-chatbot/agent-adapter.d.ts +1 -0
  7. package/dist/ai-chatbot/ai-chatbot-base.mjs +0 -1
  8. package/dist/ai-chatbot/ai-chatbot.d.ts +5 -1
  9. package/dist/ai-chatbot/ai-chatbot.mjs +18 -5
  10. package/dist/ai-chatbot/index.d.ts +1 -0
  11. package/dist/ai-chatbot/index.mjs +4 -1
  12. package/dist/ai-chatbot/message-state-controller.d.ts +8 -11
  13. package/dist/ai-chatbot/message-state-controller.mjs +7 -129
  14. package/dist/ai-chatbot/request-serialization.d.ts +33 -0
  15. package/dist/ai-chatbot/request-serialization.mjs +31 -0
  16. package/dist/ai-chatbot/types.d.ts +0 -73
  17. package/dist/ai-chatbot-launcher/ai-chatbot-launcher.d.ts +14 -2
  18. package/dist/ai-chatbot-launcher/ai-chatbot-launcher.mjs +86 -12
  19. package/dist/ai-chatbot-launcher/ai-chatbot-launcher.scss.mjs +1 -1
  20. package/dist/ai-conversations-panel/ai-conversations-panel.d.ts +10 -0
  21. package/dist/ai-conversations-panel/ai-conversations-panel.mjs +68 -7
  22. package/dist/ai-conversations-panel/ai-conversations-panel.scss.mjs +1 -1
  23. package/dist/ai-dialog/ai-dialog.mjs +1 -1
  24. package/dist/ai-dropdown-menu/ai-dropdown-menu.mjs +1 -1
  25. package/dist/ai-dropdown-menu/index.d.ts +1 -1
  26. package/dist/ai-edit-thread/ai-edit-thread.d.ts +5 -0
  27. package/dist/ai-edit-thread/ai-edit-thread.mjs +16 -9
  28. package/dist/ai-error-message/ai-error-message.d.ts +7 -1
  29. package/dist/ai-error-message/ai-error-message.mjs +40 -5
  30. package/dist/ai-error-message/ai-error-message.scss.mjs +1 -1
  31. package/dist/ai-error-message/index.d.ts +1 -0
  32. package/dist/ai-fab/ai-fab.d.ts +2 -0
  33. package/dist/ai-fab/ai-fab.mjs +5 -1
  34. package/dist/ai-icon-button/ai-icon-button.mjs +4 -1
  35. package/dist/ai-message-thread/ai-message-thread.mjs +1 -1
  36. package/dist/ai-sidebar/ai-sidebar.mjs +2 -2
  37. package/dist/ai-thread-actions-menu/ai-thread-actions-menu.d.ts +5 -0
  38. package/dist/ai-thread-actions-menu/ai-thread-actions-menu.mjs +6 -2
  39. package/dist/ai-threads-search/ai-threads-search.d.ts +26 -2
  40. package/dist/ai-threads-search/ai-threads-search.mjs +155 -35
  41. package/dist/ai-threads-search/ai-threads-search.scss.mjs +1 -1
  42. package/dist/ai-tool-call-indicator/ai-tool-call-indicator.scss.mjs +1 -1
  43. package/dist/ai-voice-input/ai-voice-input.mjs +2 -2
  44. package/dist/core/chatbot-core-controller.mjs +6 -8
  45. package/dist/core/popover/popover.mjs +3 -0
  46. package/dist/index.mjs +4 -1
  47. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { unsafeCSS, LitElement, html } from "lit";
1
+ import { unsafeCSS, LitElement, html, nothing } from "lit";
2
2
  import { property, query, state, customElement } from "lit/decorators.js";
3
3
  import { when } from "lit/directives/when.js";
4
4
  import { formatRelativeTime } from "../utils.mjs";
@@ -6,6 +6,7 @@ import { DeleteThreadController } from "../utils/delete-thread-controller.mjs";
6
6
  import { InfiniteScrollController } from "../utils/infinite-scroll-controller.mjs";
7
7
  import "../ai-thread-actions-menu/ai-thread-actions-menu.mjs";
8
8
  import "../ai-edit-thread/ai-edit-thread.mjs";
9
+ import "../ai-error-message/ai-error-message.mjs";
9
10
  import "../ai-spinner/ai-spinner.mjs";
10
11
  import "../ai-modal/ai-modal.mjs";
11
12
  import styles from "./ai-threads-search.scss.mjs";
@@ -25,8 +26,9 @@ var __decorateClass = (decorators, target, key, kind) => {
25
26
  var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
26
27
  var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
27
28
  var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
29
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
28
30
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
29
- var _deleteThreadController, _infiniteScrollController, _AiThreadsSearchComponent_instances, shouldEnablePagination_get, handleSearchInput_fn, performSearch_fn, loadMore_fn, handleThreadSelect_fn, handleNewChatClick_fn, handleBackClick_fn, handleMenuRename_fn, handleMenuOpen_fn, handleMenuClose_fn, handleMenuDeleteClick_fn, confirmDelete_fn, handleEditSave_fn, handleEditCancel_fn, handleClearSearch_fn, displayedThreads_get, header_get, searchField_get, renderThreadItem_fn, resultsList_get;
31
+ var _focusRowIndexAfterLoad, _deleteThreadController, _infiniteScrollController, _AiThreadsSearchComponent_instances, shouldEnablePagination_get, handleSearchInput_fn, performSearch_fn, handleLoadMoreClick_fn, focusRow_fn, loadMore_fn, handleThreadSelect_fn, handleNewChatClick_fn, handleBackClick_fn, handleRetryClick_fn, handleMenuRename_fn, handleMenuOpen_fn, handleMenuClose_fn, handleMenuDeleteClick_fn, confirmDelete_fn, handleEditSave_fn, handleEditCancel_fn, handleClearSearch_fn, displayedThreads_get, header_get, searchField_get, renderThreadItem_fn, errorState_get, compactError_get, errorPlacement_get, emptyState_get, listFooter_get, resultsList_get;
30
32
  const AiThreadsSearchComponentTagName = "forge-ai-threads-search";
31
33
  let AiThreadsSearchComponent = class extends LitElement {
32
34
  constructor() {
@@ -38,8 +40,7 @@ let AiThreadsSearchComponent = class extends LitElement {
38
40
  this.showBackButton = false;
39
41
  this.showSearch = true;
40
42
  this.totalChats = 0;
41
- this.placeholder = "Search chats...";
42
- this.emptyMessage = "No chats found";
43
+ this.placeholder = "Find a chat";
43
44
  this.showThreadRename = false;
44
45
  this.showThreadDelete = false;
45
46
  this.selectedThreadId = null;
@@ -49,6 +50,7 @@ let AiThreadsSearchComponent = class extends LitElement {
49
50
  this._editingThreadId = null;
50
51
  this._hiddenThreadIds = /* @__PURE__ */ new Set();
51
52
  this._openMenuThreadId = null;
53
+ __privateAdd(this, _focusRowIndexAfterLoad, null);
52
54
  __privateAdd(this, _deleteThreadController, new DeleteThreadController(this, {
53
55
  onConfirm: (thread) => __privateMethod(this, _AiThreadsSearchComponent_instances, confirmDelete_fn).call(this, thread)
54
56
  }));
@@ -85,15 +87,38 @@ let AiThreadsSearchComponent = class extends LitElement {
85
87
  confirmThreadDelete(thread) {
86
88
  __privateMethod(this, _AiThreadsSearchComponent_instances, confirmDelete_fn).call(this, thread);
87
89
  }
90
+ /**
91
+ * Discards the current query and its results so the list shows every loaded chat again.
92
+ * Dispatches no search event and moves no focus, so a host may call it on a surface that is
93
+ * currently hidden (e.g. a popover that stays mounted between opens).
94
+ */
95
+ resetSearch() {
96
+ clearTimeout(this._searchTimeout);
97
+ this._searchQuery = "";
98
+ this._searchResults = [];
99
+ this._isSearching = false;
100
+ __privateSet(this, _focusRowIndexAfterLoad, null);
101
+ __privateGet(this, _infiniteScrollController).reset();
102
+ }
103
+ /**
104
+ * Moves focus into the component: the search input when shown, otherwise the first chat in
105
+ * the list, otherwise the first header button.
106
+ */
107
+ focus() {
108
+ const target = this._searchInput ?? this.shadowRoot?.querySelector(".forge-list-item > button") ?? this.shadowRoot?.querySelector("button");
109
+ target?.focus();
110
+ }
88
111
  render() {
89
112
  return html`
90
113
  <div class="threads-search-container">
91
- ${__privateGet(this, _AiThreadsSearchComponent_instances, header_get)} ${when(this.showSearch, () => __privateGet(this, _AiThreadsSearchComponent_instances, searchField_get))} ${__privateGet(this, _AiThreadsSearchComponent_instances, resultsList_get)}
114
+ ${__privateGet(this, _AiThreadsSearchComponent_instances, header_get)} ${when(this.showSearch, () => __privateGet(this, _AiThreadsSearchComponent_instances, searchField_get))}
115
+ ${when(__privateGet(this, _AiThreadsSearchComponent_instances, errorPlacement_get) === "above-list", () => __privateGet(this, _AiThreadsSearchComponent_instances, compactError_get))} ${__privateGet(this, _AiThreadsSearchComponent_instances, resultsList_get)}
92
116
  </div>
93
117
  ${__privateGet(this, _deleteThreadController).template}
94
118
  `;
95
119
  }
96
120
  };
121
+ _focusRowIndexAfterLoad = /* @__PURE__ */ new WeakMap();
97
122
  _deleteThreadController = /* @__PURE__ */ new WeakMap();
98
123
  _infiniteScrollController = /* @__PURE__ */ new WeakMap();
99
124
  _AiThreadsSearchComponent_instances = /* @__PURE__ */ new WeakSet();
@@ -114,6 +139,7 @@ handleSearchInput_fn = function(e) {
114
139
  };
115
140
  performSearch_fn = function() {
116
141
  const searchQuery = this._searchQuery.trim();
142
+ __privateSet(this, _focusRowIndexAfterLoad, null);
117
143
  __privateGet(this, _infiniteScrollController).reset();
118
144
  const searchEvent = new CustomEvent("forge-ai-threads-search-query", {
119
145
  detail: {
@@ -137,11 +163,25 @@ performSearch_fn = function() {
137
163
  __privateGet(this, _infiniteScrollController).setHasMore(false);
138
164
  }
139
165
  };
166
+ handleLoadMoreClick_fn = function() {
167
+ if (__privateGet(this, _infiniteScrollController).isLoadingMore) {
168
+ return;
169
+ }
170
+ __privateSet(this, _focusRowIndexAfterLoad, __privateGet(this, _AiThreadsSearchComponent_instances, displayedThreads_get).length);
171
+ __privateGet(this, _infiniteScrollController).setLoadingState(true);
172
+ __privateMethod(this, _AiThreadsSearchComponent_instances, loadMore_fn).call(this);
173
+ };
174
+ focusRow_fn = function(index) {
175
+ const rows = this.shadowRoot?.querySelectorAll(".forge-list-item > button") ?? [];
176
+ rows[index]?.focus();
177
+ };
140
178
  loadMore_fn = function() {
141
179
  const loadMoreEvent = new CustomEvent("forge-ai-threads-search-load-more", {
142
180
  detail: {
143
181
  query: this._searchQuery,
144
182
  appendResults: (results) => {
183
+ const focusRowIndex = __privateGet(this, _focusRowIndexAfterLoad);
184
+ __privateSet(this, _focusRowIndexAfterLoad, null);
145
185
  if (results.length === 0) {
146
186
  __privateGet(this, _infiniteScrollController).setHasMore(false);
147
187
  } else {
@@ -150,6 +190,9 @@ loadMore_fn = function() {
150
190
  } else {
151
191
  this.threads = [...this.threads, ...results];
152
192
  }
193
+ if (focusRowIndex !== null) {
194
+ void this.updateComplete.then(() => __privateMethod(this, _AiThreadsSearchComponent_instances, focusRow_fn).call(this, focusRowIndex));
195
+ }
153
196
  }
154
197
  __privateGet(this, _infiniteScrollController).setLoadingState(false);
155
198
  }
@@ -181,6 +224,17 @@ handleBackClick_fn = function() {
181
224
  });
182
225
  this.dispatchEvent(event);
183
226
  };
227
+ handleRetryClick_fn = function() {
228
+ if (this._searchQuery.trim()) {
229
+ __privateMethod(this, _AiThreadsSearchComponent_instances, performSearch_fn).call(this);
230
+ return;
231
+ }
232
+ const event = new CustomEvent("forge-ai-threads-search-retry", {
233
+ bubbles: true,
234
+ composed: true
235
+ });
236
+ this.dispatchEvent(event);
237
+ };
184
238
  handleMenuRename_fn = function(e) {
185
239
  this._editingThreadId = e.detail.id;
186
240
  this._openMenuThreadId = null;
@@ -269,10 +323,8 @@ handleEditCancel_fn = function() {
269
323
  this._editingThreadId = null;
270
324
  };
271
325
  handleClearSearch_fn = function() {
272
- this._searchQuery = "";
273
- this._searchResults = [];
274
- this._isSearching = false;
275
- __privateGet(this, _infiniteScrollController).reset();
326
+ this.resetSearch();
327
+ this._searchInput?.focus();
276
328
  };
277
329
  displayedThreads_get = function() {
278
330
  const threads = this._searchQuery ? this._searchResults : this.threads;
@@ -326,16 +378,22 @@ searchField_get = function() {
326
378
  d="M15.5 14h-.79l-.28-.27A6.47 6.47 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14" />
327
379
  </svg>
328
380
  <input
381
+ id="threads-search-input"
329
382
  type="text"
330
383
  autocomplete="off"
331
- .value=${this._searchQuery}
384
+ aria-label="Search"
332
385
  placeholder=${this.placeholder}
386
+ .value=${this._searchQuery}
333
387
  @input=${__privateMethod(this, _AiThreadsSearchComponent_instances, handleSearchInput_fn)} />
334
388
  ${when(this._isSearching, () => html`<forge-ai-spinner size="extra-small"></forge-ai-spinner>`)}
335
389
  ${when(
336
390
  this._searchQuery.length,
337
391
  () => html`
338
- <button class="forge-icon-button forge-icon-button--small" type="button" @click=${__privateMethod(this, _AiThreadsSearchComponent_instances, handleClearSearch_fn)}>
392
+ <button
393
+ class="forge-icon-button forge-icon-button--small"
394
+ type="button"
395
+ aria-label="Clear search"
396
+ @click=${__privateMethod(this, _AiThreadsSearchComponent_instances, handleClearSearch_fn)}>
339
397
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="forge-icon" aria-hidden="true">
340
398
  <path
341
399
  d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" />
@@ -365,7 +423,10 @@ renderThreadItem_fn = function(thread) {
365
423
  </forge-ai-edit-thread>
366
424
  `,
367
425
  () => html`
368
- <button type="button" aria-selected=${isSelected} @click=${() => __privateMethod(this, _AiThreadsSearchComponent_instances, handleThreadSelect_fn).call(this, thread)}>
426
+ <button
427
+ type="button"
428
+ aria-current=${isSelected ? "true" : nothing}
429
+ @click=${() => __privateMethod(this, _AiThreadsSearchComponent_instances, handleThreadSelect_fn).call(this, thread)}>
369
430
  ${thread.title}
370
431
  </button>
371
432
  <span class="thread-time">${formatRelativeTime(thread.createdAt)}</span>
@@ -390,31 +451,87 @@ renderThreadItem_fn = function(thread) {
390
451
  </li>
391
452
  `;
392
453
  };
454
+ errorState_get = function() {
455
+ return html`
456
+ <div class="error-state">
457
+ <forge-ai-error-message density="small">
458
+ <span slot="title">Error</span>
459
+ <p class="error-state__message">${this.errorMessage}</p>
460
+ <button
461
+ class="forge-button forge-button--outlined forge-button--dense error-state__retry"
462
+ type="button"
463
+ @click=${__privateMethod(this, _AiThreadsSearchComponent_instances, handleRetryClick_fn)}>
464
+ Retry
465
+ </button>
466
+ </forge-ai-error-message>
467
+ </div>
468
+ `;
469
+ };
470
+ compactError_get = function() {
471
+ return html`
472
+ <forge-ai-error-message class="compact-error" density="small">
473
+ <div class="compact-error__body">
474
+ <span>${this.errorMessage}</span>
475
+ <button
476
+ class="forge-button forge-button--outlined forge-button--dense compact-error__retry"
477
+ type="button"
478
+ @click=${__privateMethod(this, _AiThreadsSearchComponent_instances, handleRetryClick_fn)}>
479
+ Retry
480
+ </button>
481
+ </div>
482
+ </forge-ai-error-message>
483
+ `;
484
+ };
485
+ errorPlacement_get = function() {
486
+ if (!this.errorMessage || __privateGet(this, _AiThreadsSearchComponent_instances, displayedThreads_get).length === 0) {
487
+ return "none";
488
+ }
489
+ return __privateGet(this, _infiniteScrollController).isLoadingMore ? "list-footer" : "above-list";
490
+ };
491
+ emptyState_get = function() {
492
+ const message = this._searchQuery.trim() ? "No chats found" : "No chats yet";
493
+ return html`
494
+ <div class="empty-state">
495
+ <p>${message}</p>
496
+ </div>
497
+ `;
498
+ };
499
+ listFooter_get = function() {
500
+ if (__privateGet(this, _AiThreadsSearchComponent_instances, errorPlacement_get) === "list-footer") {
501
+ return __privateGet(this, _AiThreadsSearchComponent_instances, compactError_get);
502
+ }
503
+ if (__privateGet(this, _infiniteScrollController).isLoadingMore) {
504
+ return html`
505
+ <div class="loading-more-indicator">
506
+ <forge-ai-spinner></forge-ai-spinner>
507
+ </div>
508
+ `;
509
+ }
510
+ if (__privateGet(this, _AiThreadsSearchComponent_instances, shouldEnablePagination_get) && __privateGet(this, _infiniteScrollController).hasMoreResults) {
511
+ return html`
512
+ <div class="loading-more-indicator">
513
+ <button
514
+ type="button"
515
+ class="forge-button forge-button--dense load-more-button"
516
+ @click=${__privateMethod(this, _AiThreadsSearchComponent_instances, handleLoadMoreClick_fn)}>
517
+ Load more
518
+ </button>
519
+ </div>
520
+ `;
521
+ }
522
+ return nothing;
523
+ };
393
524
  resultsList_get = function() {
394
525
  const displayThreads = __privateGet(this, _AiThreadsSearchComponent_instances, displayedThreads_get);
526
+ if (displayThreads.length === 0) {
527
+ return html`<div class="results-container">${this.errorMessage ? __privateGet(this, _AiThreadsSearchComponent_instances, errorState_get) : __privateGet(this, _AiThreadsSearchComponent_instances, emptyState_get)}</div>`;
528
+ }
395
529
  return html`
396
530
  <div class="results-container">
397
- ${when(
398
- displayThreads.length === 0,
399
- () => html`
400
- <div class="empty-state">
401
- <p>${this.emptyMessage}</p>
402
- </div>
403
- `,
404
- () => html`
405
- <ul class="forge-list forge-list--dense forge-list--navlist" role="list">
406
- ${displayThreads.map((thread) => __privateMethod(this, _AiThreadsSearchComponent_instances, renderThreadItem_fn).call(this, thread))}
407
- </ul>
408
- ${when(
409
- __privateGet(this, _infiniteScrollController).isLoadingMore,
410
- () => html`
411
- <div class="loading-more-indicator">
412
- <forge-ai-spinner></forge-ai-spinner>
413
- </div>
414
- `
415
- )}
416
- `
417
- )}
531
+ <ul class="forge-list forge-list--dense forge-list--navlist" role="list">
532
+ ${displayThreads.map((thread) => __privateMethod(this, _AiThreadsSearchComponent_instances, renderThreadItem_fn).call(this, thread))}
533
+ </ul>
534
+ ${__privateGet(this, _AiThreadsSearchComponent_instances, listFooter_get)}
418
535
  </div>
419
536
  `;
420
537
  };
@@ -441,8 +558,8 @@ __decorateClass([
441
558
  property({ type: String })
442
559
  ], AiThreadsSearchComponent.prototype, "placeholder", 2);
443
560
  __decorateClass([
444
- property({ type: String, attribute: "empty-message" })
445
- ], AiThreadsSearchComponent.prototype, "emptyMessage", 2);
561
+ property({ type: String, attribute: "error-message" })
562
+ ], AiThreadsSearchComponent.prototype, "errorMessage", 2);
446
563
  __decorateClass([
447
564
  property({ type: Boolean, attribute: "show-thread-rename" })
448
565
  ], AiThreadsSearchComponent.prototype, "showThreadRename", 2);
@@ -455,6 +572,9 @@ __decorateClass([
455
572
  __decorateClass([
456
573
  query(".results-container")
457
574
  ], AiThreadsSearchComponent.prototype, "_resultsContainer", 2);
575
+ __decorateClass([
576
+ query("#threads-search-input")
577
+ ], AiThreadsSearchComponent.prototype, "_searchInput", 2);
458
578
  __decorateClass([
459
579
  state()
460
580
  ], AiThreadsSearchComponent.prototype, "_searchQuery", 2);