@tylertech/forge-ai 0.14.0 → 0.15.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.
Files changed (46) hide show
  1. package/custom-elements.json +520 -278
  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 +47 -11
  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 +13 -1
  40. package/dist/ai-threads-search/ai-threads-search.mjs +87 -30
  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/index.mjs +4 -1
  46. package/package.json +1 -1
@@ -58,7 +58,7 @@ class MessageStateController {
58
58
  this.#updateResponseInItems();
59
59
  this.#notifyStateChange();
60
60
  }
61
- addTextToResponse(messageId, content, event) {
61
+ addTextToResponse(messageId, content) {
62
62
  const response = this._activeResponse ?? this.startResponse();
63
63
  response.isThinking = false;
64
64
  const lastChild = response.children[response.children.length - 1];
@@ -68,18 +68,10 @@ class MessageStateController {
68
68
  } else {
69
69
  response.children.push({ type: "text", messageId, content, status: "streaming" });
70
70
  }
71
- if (event) {
72
- this.#appendEventToResponse({
73
- type: "message-start",
74
- timestamp: Date.now(),
75
- data: event,
76
- rawEvent: event.rawEvent
77
- });
78
- }
79
71
  this.#updateResponseInItems();
80
72
  this.#notifyStateChange();
81
73
  }
82
- appendTextDelta(messageId, delta, event) {
74
+ appendTextDelta(messageId, delta) {
83
75
  const response = this._activeResponse ?? this.startResponse();
84
76
  response.isThinking = false;
85
77
  const lastChild = response.children[response.children.length - 1];
@@ -88,18 +80,10 @@ class MessageStateController {
88
80
  } else {
89
81
  response.children.push({ type: "text", messageId, content: delta, status: "streaming" });
90
82
  }
91
- if (event) {
92
- this.#appendEventToResponse({
93
- type: "message-delta",
94
- timestamp: Date.now(),
95
- data: event,
96
- rawEvent: event.rawEvent
97
- });
98
- }
99
83
  this.#updateResponseInItems();
100
84
  this.#notifyStateChange();
101
85
  }
102
- markTextComplete(messageId, event) {
86
+ markTextComplete(messageId) {
103
87
  if (!this._activeResponse) {
104
88
  return;
105
89
  }
@@ -107,37 +91,19 @@ class MessageStateController {
107
91
  if (textChild && textChild.type === "text") {
108
92
  textChild.status = "complete";
109
93
  }
110
- if (event) {
111
- this.#appendEventToResponse({
112
- type: "message-end",
113
- timestamp: Date.now(),
114
- data: event,
115
- rawEvent: event.rawEvent
116
- });
117
- }
118
94
  this.#updateResponseInItems();
119
95
  this.#notifyStateChange();
120
96
  }
121
- addToolCallToResponse(toolCall, event) {
97
+ addToolCallToResponse(toolCall) {
122
98
  const response = this._activeResponse ?? this.startResponse();
123
99
  response.isThinking = false;
124
100
  const toolCallWithTimestamp = { ...toolCall, startTimestamp: Date.now() };
125
101
  this._toolCalls.set(toolCall.id, toolCallWithTimestamp);
126
102
  response.children.push({ type: "toolCall", data: toolCallWithTimestamp });
127
- if (event) {
128
- const streamEvent = {
129
- type: "tool-call-start",
130
- timestamp: Date.now(),
131
- data: event,
132
- rawEvent: event.rawEvent
133
- };
134
- this.#appendEventToResponse(streamEvent);
135
- this.#appendEventToToolCallInResponse(toolCall.id, streamEvent);
136
- }
137
103
  this.#updateResponseInItems();
138
104
  this.#notifyStateChange();
139
105
  }
140
- updateToolCallInResponse(toolCallId, updates, eventData) {
106
+ updateToolCallInResponse(toolCallId, updates) {
141
107
  const toolCall = this._toolCalls.get(toolCallId);
142
108
  if (!toolCall) {
143
109
  return;
@@ -157,29 +123,6 @@ class MessageStateController {
157
123
  }
158
124
  }
159
125
  }
160
- if (eventData) {
161
- if (eventData.eventType === "tool-call-args") {
162
- const argsEvent = eventData.event;
163
- const streamEvent = {
164
- type: "tool-call-args",
165
- timestamp: Date.now(),
166
- data: argsEvent,
167
- rawEvent: argsEvent.rawEvent
168
- };
169
- this.#appendEventToResponse(streamEvent);
170
- this.#appendEventToToolCallInResponse(toolCallId, streamEvent);
171
- } else {
172
- const endEvent = eventData.event;
173
- const streamEvent = {
174
- type: "tool-call-end",
175
- timestamp: Date.now(),
176
- data: endEvent,
177
- rawEvent: endEvent.rawEvent
178
- };
179
- this.#appendEventToResponse(streamEvent);
180
- this.#appendEventToToolCallInResponse(toolCallId, streamEvent);
181
- }
182
- }
183
126
  this.#updateResponseInItems();
184
127
  this.#notifyStateChange();
185
128
  }
@@ -188,16 +131,6 @@ class MessageStateController {
188
131
  result,
189
132
  status: event?.isError === true ? "error" : "complete"
190
133
  });
191
- if (event) {
192
- const streamEvent = {
193
- type: "tool-result",
194
- timestamp: Date.now(),
195
- data: event,
196
- rawEvent: event.rawEvent
197
- };
198
- this.#appendEventToResponse(streamEvent);
199
- this.#appendEventToToolCallInResponse(toolCallId, streamEvent);
200
- }
201
134
  }
202
135
  tryFinalizeResponse() {
203
136
  if (!this._activeResponse) {
@@ -221,29 +154,6 @@ class MessageStateController {
221
154
  return item;
222
155
  });
223
156
  }
224
- #appendEventToResponse(event) {
225
- if (!this._activeResponse) {
226
- return;
227
- }
228
- this._activeResponse.eventStream = [...this._activeResponse.eventStream || [], event];
229
- }
230
- #appendEventToToolCallInResponse(toolCallId, event) {
231
- const toolCall = this._toolCalls.get(toolCallId);
232
- if (!toolCall) {
233
- return;
234
- }
235
- const eventStream = [...toolCall.eventStream || [], event];
236
- const updated = { ...toolCall, eventStream };
237
- this._toolCalls.set(toolCallId, updated);
238
- if (this._activeResponse) {
239
- for (const child of this._activeResponse.children) {
240
- if (child.type === "toolCall" && child.data.id === toolCallId) {
241
- child.data = updated;
242
- break;
243
- }
244
- }
245
- }
246
- }
247
157
  #notifyStateChange() {
248
158
  this._host.requestUpdate();
249
159
  }
@@ -251,21 +161,13 @@ class MessageStateController {
251
161
  this._messageItems = [...this._messageItems, item];
252
162
  this.#notifyStateChange();
253
163
  }
254
- addMessage(message, event) {
164
+ addMessage(message) {
255
165
  this.tryFinalizeResponse();
256
166
  const existing = this.getMessage(message.id);
257
167
  if (existing) {
258
168
  return;
259
169
  }
260
170
  this.addMessageItem({ type: "message", data: message });
261
- if (event) {
262
- this.#appendEventToMessage(message.id, {
263
- type: "message-start",
264
- timestamp: Date.now(),
265
- data: event,
266
- rawEvent: event.rawEvent
267
- });
268
- }
269
171
  if (message.status === "complete" || message.status === "error") {
270
172
  this._config.onThreadSettled?.();
271
173
  }
@@ -308,21 +210,13 @@ class MessageStateController {
308
210
  this._messageItems = this._messageItems.filter((item) => !(item.type === "message" && item.data.id === id));
309
211
  this.#notifyStateChange();
310
212
  }
311
- updateMessageStatus(id, status, event) {
213
+ updateMessageStatus(id, status) {
312
214
  this._messageItems = this._messageItems.map((item) => {
313
215
  if (item.type === "message" && item.data.id === id) {
314
216
  return { ...item, data: { ...item.data, status } };
315
217
  }
316
218
  return item;
317
219
  });
318
- if (event) {
319
- this.#appendEventToMessage(id, {
320
- type: "message-end",
321
- timestamp: Date.now(),
322
- data: event,
323
- rawEvent: event.rawEvent
324
- });
325
- }
326
220
  this.#notifyStateChange();
327
221
  if (status === "complete" || status === "error") {
328
222
  this._config.onThreadSettled?.();
@@ -373,7 +267,6 @@ class MessageStateController {
373
267
  timestamp: response.timestamp,
374
268
  status: response.status === "streaming" ? "streaming" : response.status === "error" ? "error" : "complete",
375
269
  toolCalls: toolCalls.length ? toolCalls : void 0,
376
- eventStream: response.eventStream,
377
270
  feedback: response.feedback,
378
271
  children: response.children
379
272
  };
@@ -418,7 +311,6 @@ class MessageStateController {
418
311
  children,
419
312
  status: msg.status === "streaming" ? "streaming" : "complete",
420
313
  timestamp: msg.timestamp,
421
- eventStream: msg.eventStream,
422
314
  feedback: msg.feedback
423
315
  };
424
316
  messageItems.push({ type: "assistant", data: response });
@@ -436,20 +328,6 @@ class MessageStateController {
436
328
  this.#notifyStateChange();
437
329
  }
438
330
  }
439
- #appendEventToMessage(id, event) {
440
- const messageExists = this._messageItems.some((item) => item.type === "message" && item.data.id === id);
441
- if (!messageExists) {
442
- return;
443
- }
444
- this._messageItems = this._messageItems.map((item) => {
445
- if (item.type === "message" && item.data.id === id) {
446
- const eventStream = [...item.data.eventStream || [], event];
447
- return { ...item, data: { ...item.data, eventStream } };
448
- }
449
- return item;
450
- });
451
- this.#notifyStateChange();
452
- }
453
331
  }
454
332
  export {
455
333
  MessageStateController
@@ -0,0 +1,33 @@
1
+ import { ChatMessage, ToolCall } from './types.js';
2
+ /**
3
+ * Wire shape for a tool call sent back to an agent endpoint: the final `args`
4
+ * and its `result`, and nothing else. Local-only fields (`eventStream`,
5
+ * `argsBuffer`, `children`, timing metadata) are deliberately absent - no
6
+ * backend reads them and they grow without bound across turns.
7
+ */
8
+ export interface RequestToolCall {
9
+ id: string;
10
+ name: string;
11
+ args: Record<string, unknown>;
12
+ status: ToolCall['status'];
13
+ result?: unknown;
14
+ }
15
+ /** Wire shape for a single message sent to an agent endpoint. */
16
+ export interface RequestMessage {
17
+ role: ChatMessage['role'];
18
+ content: string;
19
+ toolCallId?: string;
20
+ toolCalls?: RequestToolCall[];
21
+ }
22
+ /**
23
+ * Projects a tool call onto its wire shape. This is the single boundary at which
24
+ * local state is dropped, so adapters never have to remember to omit it.
25
+ */
26
+ export declare function toRequestToolCall(toolCall: ToolCall): RequestToolCall;
27
+ /**
28
+ * Projects the thread onto the messages an agent endpoint is sent: client-only
29
+ * messages and empty assistant turns are dropped, and every remaining message is
30
+ * rebuilt field by field rather than spread, so no local state can leak into a
31
+ * request body.
32
+ */
33
+ export declare function toRequestMessages(messages: ChatMessage[]): RequestMessage[];
@@ -0,0 +1,31 @@
1
+ function toRequestToolCall(toolCall) {
2
+ const { id, name, args, status, result } = toolCall;
3
+ return {
4
+ id,
5
+ name,
6
+ args,
7
+ status,
8
+ ...result === void 0 ? {} : { result }
9
+ };
10
+ }
11
+ function toRequestMessages(messages) {
12
+ return messages.filter(isSendableMessage).map((message) => ({
13
+ role: message.role,
14
+ content: message.content,
15
+ ...message.toolCallId ? { toolCallId: message.toolCallId } : {},
16
+ ...message.toolCalls?.length ? { toolCalls: message.toolCalls.map(toRequestToolCall) } : {}
17
+ }));
18
+ }
19
+ function isSendableMessage(message) {
20
+ if (message.clientOnly) {
21
+ return false;
22
+ }
23
+ if (message.role === "assistant") {
24
+ return message.content.trim().length > 0 || !!message.toolCalls?.length;
25
+ }
26
+ return true;
27
+ }
28
+ export {
29
+ toRequestMessages,
30
+ toRequestToolCall
31
+ };
@@ -100,76 +100,6 @@ export interface ToolDefinition<THandlerArgs = Record<string, unknown>> {
100
100
  */
101
101
  handler?: (context: HandlerContext<THandlerArgs>) => Promise<string | Record<string, unknown> | void> | string | Record<string, unknown> | void;
102
102
  }
103
- export interface MessageStartEvent {
104
- messageId: string;
105
- }
106
- export interface MessageDeltaEvent {
107
- messageId: string;
108
- delta: string;
109
- }
110
- export interface MessageEndEvent {
111
- messageId: string;
112
- }
113
- export interface ToolCallStartEvent {
114
- id: string;
115
- messageId: string;
116
- name: string;
117
- }
118
- export interface ToolCallArgsEvent {
119
- id: string;
120
- messageId: string;
121
- name: string;
122
- argsBuffer: string;
123
- partialArgs?: Record<string, unknown>;
124
- }
125
- export interface ToolCallEndEvent {
126
- id: string;
127
- messageId: string;
128
- name: string;
129
- args: Record<string, unknown>;
130
- }
131
- export interface ToolResultEvent {
132
- toolCallId: string;
133
- result: unknown;
134
- message: ChatMessage;
135
- isError?: boolean;
136
- }
137
- export type StreamEvent = {
138
- type: 'message-start';
139
- timestamp: number;
140
- data: MessageStartEvent;
141
- rawEvent?: unknown;
142
- } | {
143
- type: 'message-delta';
144
- timestamp: number;
145
- data: MessageDeltaEvent;
146
- rawEvent?: unknown;
147
- } | {
148
- type: 'message-end';
149
- timestamp: number;
150
- data: MessageEndEvent;
151
- rawEvent?: unknown;
152
- } | {
153
- type: 'tool-call-start';
154
- timestamp: number;
155
- data: ToolCallStartEvent;
156
- rawEvent?: unknown;
157
- } | {
158
- type: 'tool-call-args';
159
- timestamp: number;
160
- data: ToolCallArgsEvent;
161
- rawEvent?: unknown;
162
- } | {
163
- type: 'tool-call-end';
164
- timestamp: number;
165
- data: ToolCallEndEvent;
166
- rawEvent?: unknown;
167
- } | {
168
- type: 'tool-result';
169
- timestamp: number;
170
- data: ToolResultEvent;
171
- rawEvent?: unknown;
172
- };
173
103
  export interface ChatMessage {
174
104
  id: string;
175
105
  role: 'user' | 'assistant' | 'system' | 'tool';
@@ -178,7 +108,6 @@ export interface ChatMessage {
178
108
  status: 'pending' | 'streaming' | 'complete' | 'error';
179
109
  toolCalls?: ToolCall[];
180
110
  toolCallId?: string;
181
- eventStream?: StreamEvent[];
182
111
  feedback?: ResponseFeedback;
183
112
  children?: ResponseItem[];
184
113
  clientOnly?: boolean;
@@ -217,7 +146,6 @@ export interface ToolCall<TArgs = Record<string, unknown>> {
217
146
  result?: unknown;
218
147
  status: 'pending' | 'parsing' | 'executing' | 'complete' | 'error';
219
148
  type: ToolType;
220
- eventStream?: StreamEvent[];
221
149
  startTimestamp?: number;
222
150
  endTimestamp?: number;
223
151
  }
@@ -240,7 +168,6 @@ export interface AssistantResponse {
240
168
  children: ResponseItem[];
241
169
  status: 'streaming' | 'complete' | 'error';
242
170
  timestamp: number;
243
- eventStream?: StreamEvent[];
244
171
  feedback?: ResponseFeedback;
245
172
  isThinking?: boolean;
246
173
  }
@@ -46,6 +46,7 @@ declare global {
46
46
  'forge-ai-chatbot-launcher-thread-search': CustomEvent<ForgeAiChatbotLauncherThreadSearchEventData>;
47
47
  'forge-ai-chatbot-launcher-thread-load-more': CustomEvent<ForgeAiChatbotLauncherThreadLoadMoreEventData>;
48
48
  'forge-ai-chatbot-launcher-new-chat': CustomEvent<void>;
49
+ 'forge-ai-chatbot-launcher-thread-retry': CustomEvent<void>;
49
50
  }
50
51
  }
51
52
  export declare const AiChatbotLauncherComponentTagName: keyof HTMLElementTagNameMap;
@@ -72,11 +73,13 @@ export declare const AiChatbotLauncherComponentTagName: keyof HTMLElementTagName
72
73
  * @property {Agent[]} agents - List of available agents for selector
73
74
  * @property {string} selectedAgentId - Currently selected agent ID
74
75
  * @property {string} threadName - The name of the current thread (shown in conversation view breadcrumb)
76
+ * @property {boolean} showHistoryButton - Controls the prompt bar's chat history button and its popover (default: false). Visibility is yours to decide - the component never infers it from whether chats are loaded, so an empty `threads` shows "No chats yet" rather than removing the entry point
75
77
  * @property {boolean} showThreadRename - Whether to show the rename option in thread actions menu
76
78
  * @property {boolean} showThreadDelete - Whether to show the delete option in thread actions menu
77
79
  * @property {Thread[]} threads - The list of chats shown in the history popover and full history view
78
80
  * @property {number} totalThreads - Total number of chats available. When greater than the number of loaded threads, infinite scroll is enabled (0 disables it)
79
- * @property {boolean} threadsLoading - Whether chats are currently loading, used to gate the history button and its disabled state
81
+ * @property {boolean} threadsLoading - Whether chats are currently loading, used to show a spinner on the history button and disable it
82
+ * @property {string | undefined} threadsError - Message describing a failed chat history load. When set, the history popover/view shows the message with a retry button instead of the empty state, and the history button shows an error badge so the failure is visible with the popover closed. If chats are already loaded the list stays visible and the message renders as a compact single line with a retry - at the bottom of the list when a page was in flight, otherwise above it. Clear it once a load succeeds.
80
83
  * @property {string | null} selectedThreadId - The id of the currently selected thread, highlighted in the history popover/view. Updated internally when a thread is selected or a new chat starts.
81
84
  *
82
85
  * @cssproperty --forge-ai-chatbot-launcher-icon-color - The fill color for the AI icon.
@@ -94,10 +97,11 @@ export declare const AiChatbotLauncherComponentTagName: keyof HTMLElementTagName
94
97
  * @event {CustomEvent<ForgeAiChatbotAgentChangeEventData>} forge-ai-chatbot-agent-change - Fired when agent selection changes
95
98
  * @event {CustomEvent<ForgeAiChatbotLauncherThreadRenameEventData>} forge-ai-chatbot-launcher-thread-rename - Fired when thread rename is saved. Parent should update threadName property and call onSuccess() or onError()
96
99
  * @event {CustomEvent<ForgeAiChatbotLauncherThreadDeleteEventData>} forge-ai-chatbot-launcher-thread-delete - Fired when thread deletion is confirmed. Parent should delete thread and call onSuccess() or onError()
97
- * @event {CustomEvent<ForgeAiChatbotLauncherThreadSelectEventData>} forge-ai-chatbot-launcher-thread-select - Fired when a thread is selected from the history popover or full history view
100
+ * @event {CustomEvent<ForgeAiChatbotLauncherThreadSelectEventData>} forge-ai-chatbot-launcher-thread-select - Fired when a thread is selected from the history popover or full history view. Cancelable - prevents selectedThreadId from being set and the transition to the conversation view, leaving the host to commit both once its own load resolves
98
101
  * @event {CustomEvent<ForgeAiChatbotLauncherThreadSearchEventData>} forge-ai-chatbot-launcher-thread-search - Fired when the history search query changes (debounced). Cancelable - call setResults() with the results
99
102
  * @event {CustomEvent<ForgeAiChatbotLauncherThreadLoadMoreEventData>} forge-ai-chatbot-launcher-thread-load-more - Fired when scrolling near the bottom of the history list for pagination. Call appendResults() with the next page (empty array signals no more results)
100
103
  * @event {CustomEvent<void>} forge-ai-chatbot-launcher-new-chat - Fired when "New chat" is clicked from the full history view. Cancelable - prevents startNewChat() from being called
104
+ * @event {CustomEvent<void>} forge-ai-chatbot-launcher-thread-retry - Fired when the retry button in the history error state is clicked. Re-request the chats and clear threadsError once the load succeeds
101
105
  */
102
106
  export declare class AiChatbotLauncherComponent extends AiChatbotBase {
103
107
  #private;
@@ -105,11 +109,13 @@ export declare class AiChatbotLauncherComponent extends AiChatbotBase {
105
109
  placeholder: string;
106
110
  descriptionText?: string;
107
111
  threadName?: string;
112
+ showHistoryButton: boolean;
108
113
  showThreadRename: boolean;
109
114
  showThreadDelete: boolean;
110
115
  threads: Thread[];
111
116
  totalThreads: number;
112
117
  threadsLoading: boolean;
118
+ threadsError?: string;
113
119
  selectedThreadId: string | null;
114
120
  private _viewState;
115
121
  private _skipAnimation;
@@ -120,6 +126,12 @@ export declare class AiChatbotLauncherComponent extends AiChatbotBase {
120
126
  constructor();
121
127
  protected _onConnected(): void;
122
128
  startConversation(): void;
129
+ /**
130
+ * Returns to the welcome view without touching conversation state. Unlike `startNewChat()`, this
131
+ * leaves messages, `selectedThreadId`, and the adapter's thread untouched, so an abandoned thread
132
+ * selection can be undone without discarding the conversation the user was already in.
133
+ */
134
+ showWelcome(): void;
123
135
  /**
124
136
  * Navigates directly to the full history view (not the history popover).
125
137
  */
@@ -40,7 +40,7 @@ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read fr
40
40
  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);
41
41
  var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
42
42
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
43
- var _headerRef, _gradientContainerRef, _internals, _pendingThreadDeleteSource, _deleteThreadController, _AiChatbotLauncherComponent_instances, setViewState_fn, transitionToConversation_fn, commitConversationTransition_fn, transitionToWelcome_fn, transitionToHistory_fn, handleHistoryBack_fn, handleHeaderClear_fn, handleHeaderInfo_fn, handleThreadRename_fn, handleEditSave_fn, handleEditCancel_fn, handleThreadNameClick_fn, handleThreadDelete_fn, handleHistoryButtonClick_fn, handleHistoryPopoverToggle_fn, handleViewAllClick_fn, handleThreadsSearchQuery_fn, handleThreadsSearchLoadMore_fn, handleThreadsSearchSelect_fn, handleThreadsSearchNewChat_fn, handleThreadsSearchRename_fn, handleThreadsSearchDelete_fn, handleThreadsSearchDeleteConfirm_fn, headingElement_get, promptTemplate_get, welcomeHeaderTemplate_get, welcomeSuggestionsTemplate_get, threadName_get, threadActionsMenu_get, conversationContentTemplate_get, historyContentTemplate_get, mainContentTemplate_get, historyButtonTemplate_get, historyPopoverTemplate_get, promptSectionTemplate_get;
43
+ var _headerRef, _gradientContainerRef, _internals, _pendingThreadDeleteSource, _deleteThreadController, _AiChatbotLauncherComponent_instances, setViewState_fn, transitionToConversation_fn, commitConversationTransition_fn, transitionToWelcome_fn, transitionToHistory_fn, handleHistoryBack_fn, handleHeaderClear_fn, handleHeaderInfo_fn, handleThreadRename_fn, handleEditSave_fn, handleEditCancel_fn, handleThreadNameClick_fn, handleThreadDelete_fn, handleHistoryButtonClick_fn, handleHistoryPopoverToggle_fn, handleViewAllClick_fn, handleThreadsSearchQuery_fn, handleThreadsSearchLoadMore_fn, handleThreadsSearchSelect_fn, handleThreadsSearchRetry_fn, handleThreadsSearchNewChat_fn, handleThreadsSearchRename_fn, handleThreadsSearchDelete_fn, handleThreadsSearchDeleteConfirm_fn, headingElement_get, promptTemplate_get, welcomeHeaderTemplate_get, welcomeSuggestionsTemplate_get, threadName_get, threadActionsMenu_get, conversationContentTemplate_get, historyContentTemplate_get, mainContentTemplate_get, showThreadsLoading_get, historyButtonTemplate_get, historyPopoverTemplate_get, promptSectionTemplate_get;
44
44
  const HISTORY_POPOVER_INSET = 48;
45
45
  const AiChatbotLauncherComponentTagName = "forge-ai-chatbot-launcher";
46
46
  let AiChatbotLauncherComponent = class extends AiChatbotBase {
@@ -48,6 +48,7 @@ let AiChatbotLauncherComponent = class extends AiChatbotBase {
48
48
  super();
49
49
  __privateAdd(this, _AiChatbotLauncherComponent_instances);
50
50
  this.placeholder = "How can I help you today?";
51
+ this.showHistoryButton = false;
51
52
  this.showThreadRename = false;
52
53
  this.showThreadDelete = false;
53
54
  this.threads = [];
@@ -102,6 +103,15 @@ let AiChatbotLauncherComponent = class extends AiChatbotBase {
102
103
  startConversation() {
103
104
  __privateMethod(this, _AiChatbotLauncherComponent_instances, commitConversationTransition_fn).call(this);
104
105
  }
106
+ /**
107
+ * Returns to the welcome view without touching conversation state. Unlike `startNewChat()`, this
108
+ * leaves messages, `selectedThreadId`, and the adapter's thread untouched, so an abandoned thread
109
+ * selection can be undone without discarding the conversation the user was already in.
110
+ */
111
+ showWelcome() {
112
+ this._historyPopoverOpen = false;
113
+ __privateMethod(this, _AiChatbotLauncherComponent_instances, transitionToWelcome_fn).call(this);
114
+ }
105
115
  /**
106
116
  * Navigates directly to the full history view (not the history popover).
107
117
  */
@@ -169,7 +179,7 @@ let AiChatbotLauncherComponent = class extends AiChatbotBase {
169
179
  ${__privateGet(this, _AiChatbotLauncherComponent_instances, mainContentTemplate_get)} ${__privateGet(this, _AiChatbotLauncherComponent_instances, promptSectionTemplate_get)}
170
180
  ${this._viewState === "welcome" ? __privateGet(this, _AiChatbotLauncherComponent_instances, welcomeSuggestionsTemplate_get) : nothing}
171
181
  </div>
172
- ${__privateGet(this, _AiChatbotLauncherComponent_instances, historyPopoverTemplate_get)} ${__privateGet(this, _deleteThreadController).template}
182
+ ${when(this.showHistoryButton, () => __privateGet(this, _AiChatbotLauncherComponent_instances, historyPopoverTemplate_get))} ${__privateGet(this, _deleteThreadController).template}
173
183
  `;
174
184
  }
175
185
  };
@@ -287,13 +297,20 @@ handleThreadsSearchLoadMore_fn = function(evt) {
287
297
  };
288
298
  handleThreadsSearchSelect_fn = function(evt) {
289
299
  this._historyPopoverOpen = false;
290
- this.selectedThreadId = evt.detail.id;
291
- this._dispatchHostEvent({
300
+ const event = this._dispatchHostEvent({
292
301
  type: "forge-ai-chatbot-launcher-thread-select",
293
- detail: evt.detail
302
+ detail: evt.detail,
303
+ cancelable: true
294
304
  });
305
+ if (event.defaultPrevented) {
306
+ return;
307
+ }
308
+ this.selectedThreadId = evt.detail.id;
295
309
  __privateMethod(this, _AiChatbotLauncherComponent_instances, setViewState_fn).call(this, "conversation");
296
310
  };
311
+ handleThreadsSearchRetry_fn = function() {
312
+ this._dispatchHostEvent({ type: "forge-ai-chatbot-launcher-thread-retry" });
313
+ };
297
314
  handleThreadsSearchNewChat_fn = function() {
298
315
  this._historyPopoverOpen = false;
299
316
  const event = this._dispatchHostEvent({ type: "forge-ai-chatbot-launcher-new-chat", cancelable: true });
@@ -416,6 +433,7 @@ threadName_get = function() {
416
433
  isEditing,
417
434
  () => html`
418
435
  <forge-ai-edit-thread
436
+ density="medium"
419
437
  .thread=${{
420
438
  id: threadId,
421
439
  title: this.threadName || "",
@@ -458,6 +476,7 @@ threadActionsMenu_get = function() {
458
476
  };
459
477
  return html`
460
478
  <forge-ai-thread-actions-menu
479
+ density="medium"
461
480
  .thread=${thread}
462
481
  ?show-rename=${this.showThreadRename}
463
482
  ?show-delete=${this.showThreadDelete}
@@ -525,6 +544,7 @@ historyContentTemplate_get = function() {
525
544
  .threads=${this.threads}
526
545
  .totalChats=${this.totalThreads}
527
546
  .selectedThreadId=${this.selectedThreadId}
547
+ .errorMessage=${this.threadsError}
528
548
  ?show-thread-rename=${this.showThreadRename}
529
549
  ?show-thread-delete=${this.showThreadDelete}
530
550
  @forge-ai-threads-search-back=${__privateMethod(this, _AiChatbotLauncherComponent_instances, handleHistoryBack_fn)}
@@ -534,6 +554,7 @@ historyContentTemplate_get = function() {
534
554
  @forge-ai-threads-search-new-chat=${__privateMethod(this, _AiChatbotLauncherComponent_instances, handleThreadsSearchNewChat_fn)}
535
555
  @forge-ai-threads-search-rename=${__privateMethod(this, _AiChatbotLauncherComponent_instances, handleThreadsSearchRename_fn)}
536
556
  @forge-ai-threads-search-delete=${__privateMethod(this, _AiChatbotLauncherComponent_instances, handleThreadsSearchDelete_fn)}
557
+ @forge-ai-threads-search-retry=${__privateMethod(this, _AiChatbotLauncherComponent_instances, handleThreadsSearchRetry_fn)}
537
558
  @forge-ai-threads-search-delete-confirm=${__privateMethod(this, _AiChatbotLauncherComponent_instances, handleThreadsSearchDeleteConfirm_fn)}>
538
559
  </forge-ai-threads-search>
539
560
  </div>
@@ -549,8 +570,11 @@ mainContentTemplate_get = function() {
549
570
  return __privateGet(this, _AiChatbotLauncherComponent_instances, conversationContentTemplate_get);
550
571
  }
551
572
  };
573
+ showThreadsLoading_get = function() {
574
+ return this.threadsLoading && !this.threadsError;
575
+ };
552
576
  historyButtonTemplate_get = function() {
553
- if (this.threads.length === 0 && this.totalThreads === 0 && !this.threadsLoading) {
577
+ if (!this.showHistoryButton) {
554
578
  return nothing;
555
579
  }
556
580
  return html`
@@ -558,13 +582,13 @@ historyButtonTemplate_get = function() {
558
582
  id="history-button"
559
583
  slot="actions-start"
560
584
  type="button"
561
- class="forge-icon-button forge-icon-button--medium ai-icon-button"
562
- aria-label="Chat history"
585
+ class="forge-icon-button forge-icon-button--medium ai-icon-button history-button"
586
+ aria-label=${this.threadsError ? "Chat history - failed to load" : "Chat history"}
563
587
  aria-expanded=${this._historyPopoverOpen}
564
- ?disabled=${this.threadsLoading}
588
+ ?disabled=${__privateGet(this, _AiChatbotLauncherComponent_instances, showThreadsLoading_get)}
565
589
  @click=${__privateMethod(this, _AiChatbotLauncherComponent_instances, handleHistoryButtonClick_fn)}>
566
590
  ${when(
567
- this.threadsLoading,
591
+ __privateGet(this, _AiChatbotLauncherComponent_instances, showThreadsLoading_get),
568
592
  () => html`<forge-ai-spinner size="extra-small"></forge-ai-spinner>`,
569
593
  () => html`
570
594
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true">
@@ -572,10 +596,14 @@ historyButtonTemplate_get = function() {
572
596
  d="M13.5 8H12v5l4.28 2.54.72-1.21-3.5-2.08zM13 3a9 9 0 0 0-9 9H1l3.96 4.03L9 12H6a7 7 0 0 1 7-7 7 7 0 0 1 7 7 7 7 0 0 1-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42A8.9 8.9 0 0 0 13 21a9 9 0 0 0 9-9 9 9 0 0 0-9-9" />
573
597
  </svg>
574
598
  `
599
+ )}
600
+ ${when(
601
+ this.threadsError,
602
+ () => html`<span class="forge-badge forge-badge--dot history-button__error-badge"></span>`
575
603
  )}
576
604
  </button>
577
605
  ${when(
578
- !this.threadsLoading,
606
+ !__privateGet(this, _AiChatbotLauncherComponent_instances, showThreadsLoading_get),
579
607
  () => html`<forge-ai-tooltip slot="actions-start" for="history-button" placement="top"
580
608
  >Chat history</forge-ai-tooltip
581
609
  >`
@@ -603,6 +631,7 @@ historyPopoverTemplate_get = function() {
603
631
  .threads=${this.threads}
604
632
  .totalChats=${this.totalThreads}
605
633
  .selectedThreadId=${this.selectedThreadId}
634
+ .errorMessage=${this.threadsError}
606
635
  ?show-thread-rename=${this.showThreadRename}
607
636
  ?show-thread-delete=${this.showThreadDelete}
608
637
  @forge-ai-threads-search-query=${__privateMethod(this, _AiChatbotLauncherComponent_instances, handleThreadsSearchQuery_fn)}
@@ -610,6 +639,7 @@ historyPopoverTemplate_get = function() {
610
639
  @forge-ai-threads-search-select=${__privateMethod(this, _AiChatbotLauncherComponent_instances, handleThreadsSearchSelect_fn)}
611
640
  @forge-ai-threads-search-rename=${__privateMethod(this, _AiChatbotLauncherComponent_instances, handleThreadsSearchRename_fn)}
612
641
  @forge-ai-threads-search-delete=${__privateMethod(this, _AiChatbotLauncherComponent_instances, handleThreadsSearchDelete_fn)}
642
+ @forge-ai-threads-search-retry=${__privateMethod(this, _AiChatbotLauncherComponent_instances, handleThreadsSearchRetry_fn)}
613
643
  @forge-ai-threads-search-delete-confirm=${__privateMethod(this, _AiChatbotLauncherComponent_instances, handleThreadsSearchDeleteConfirm_fn)}>
614
644
  <button
615
645
  slot="header-actions"
@@ -644,6 +674,9 @@ __decorateClass([
644
674
  __decorateClass([
645
675
  property({ attribute: "thread-name" })
646
676
  ], AiChatbotLauncherComponent.prototype, "threadName", 2);
677
+ __decorateClass([
678
+ property({ type: Boolean, attribute: "show-history-button" })
679
+ ], AiChatbotLauncherComponent.prototype, "showHistoryButton", 2);
647
680
  __decorateClass([
648
681
  property({ type: Boolean, attribute: "show-thread-rename" })
649
682
  ], AiChatbotLauncherComponent.prototype, "showThreadRename", 2);
@@ -659,6 +692,9 @@ __decorateClass([
659
692
  __decorateClass([
660
693
  property({ type: Boolean, attribute: "threads-loading" })
661
694
  ], AiChatbotLauncherComponent.prototype, "threadsLoading", 2);
695
+ __decorateClass([
696
+ property({ type: String, attribute: "threads-error" })
697
+ ], AiChatbotLauncherComponent.prototype, "threadsError", 2);
662
698
  __decorateClass([
663
699
  property({ type: String, attribute: "selected-thread-id" })
664
700
  ], AiChatbotLauncherComponent.prototype, "selectedThreadId", 2);