@tylertech/forge-ai 0.13.11 → 0.13.13-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +4149 -3669
- package/dist/ai-assistant-response/ai-assistant-response.mjs +3 -2
- package/dist/ai-chatbot/agent-adapter.d.ts +1 -0
- package/dist/ai-chatbot/ai-chatbot-base.d.ts +23 -1
- package/dist/ai-chatbot/ai-chatbot-base.mjs +28 -9
- package/dist/ai-chatbot/ai-chatbot-tool-call.mjs +28 -27
- package/dist/ai-chatbot/ai-chatbot.d.ts +2 -0
- package/dist/ai-chatbot/ai-chatbot.mjs +5 -0
- package/dist/ai-chatbot/index.d.ts +1 -1
- package/dist/ai-chatbot/message-state-controller.d.ts +11 -1
- package/dist/ai-chatbot/message-state-controller.mjs +54 -1
- package/dist/ai-chatbot/types.d.ts +27 -0
- package/dist/ai-chatbot/utils.d.ts +12 -1
- package/dist/ai-chatbot/utils.mjs +15 -1
- package/dist/ai-client-message/ai-client-message.d.ts +28 -0
- package/dist/ai-client-message/ai-client-message.mjs +112 -0
- package/dist/ai-client-message/ai-client-message.scss.mjs +4 -0
- package/dist/ai-client-message/index.d.ts +1 -0
- package/dist/ai-client-message/index.mjs +5 -0
- package/dist/ai-conversations-panel/ai-conversations-panel.d.ts +1 -0
- package/dist/ai-conversations-panel/ai-conversations-panel.mjs +15 -2
- package/dist/ai-conversations-panel/ai-conversations-panel.scss.mjs +1 -1
- package/dist/ai-empty-state/ai-empty-state.scss.mjs +1 -1
- package/dist/ai-message-thread/ai-message-thread.mjs +9 -1
- package/dist/ai-message-thread/ai-message-thread.scss.mjs +1 -1
- package/dist/ai-tool-call-indicator/ai-tool-call-indicator.mjs +7 -5
- package/dist/core/chatbot-core-controller.d.ts +5 -1
- package/dist/core/chatbot-core-controller.mjs +25 -10
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +3 -0
- package/dist/tools/ai-data-table/ai-data-table.d.ts +0 -1
- package/dist/tools/ai-data-table/ai-data-table.mjs +1 -13
- package/dist/tools/ai-data-table/ai-data-table.scss.mjs +1 -1
- package/dist/utils/markdown.mjs +1 -1
- package/package.json +2 -2
|
@@ -83,7 +83,7 @@ renderTextChunk_fn = function(child) {
|
|
|
83
83
|
};
|
|
84
84
|
renderToolCall_fn = function(toolCall) {
|
|
85
85
|
const toolDefinition = this.tools?.get(toolCall.name);
|
|
86
|
-
if (!toolDefinition?.renderer
|
|
86
|
+
if (!toolDefinition?.renderer) {
|
|
87
87
|
return nothing;
|
|
88
88
|
}
|
|
89
89
|
return html`<forge-ai-chatbot-tool-call
|
|
@@ -167,7 +167,8 @@ thinkingIndicator_get = function() {
|
|
|
167
167
|
return nothing;
|
|
168
168
|
}
|
|
169
169
|
const lastChild = this.response.children[this.response.children.length - 1];
|
|
170
|
-
|
|
170
|
+
const isThinking = this.response.status === "streaming" && this.response.isThinking === true;
|
|
171
|
+
if (!isThinking && lastChild?.type === "text" && lastChild.status === "streaming") {
|
|
171
172
|
const content = typeof lastChild.content === "string" ? lastChild.content : "";
|
|
172
173
|
if (content.trim().length > 0) {
|
|
173
174
|
return nothing;
|
|
@@ -10,7 +10,7 @@ import { ForgeAiSuggestionsEventData, Suggestion } from '../ai-suggestions';
|
|
|
10
10
|
import { ForgeAiVoiceInputResultEvent } from '../ai-voice-input';
|
|
11
11
|
import { AgentAdapter } from './agent-adapter.js';
|
|
12
12
|
import { ChatbotCoreController } from '../core/chatbot-core-controller.js';
|
|
13
|
-
import { Agent, ChatMessage, ContextItem, HeadingLevel, MessageItem, SlashCommand, ThreadState, ToolDefinition } from './types.js';
|
|
13
|
+
import { Agent, ChatMessage, ClientMessageInput, ContextItem, HeadingLevel, MessageItem, SlashCommand, ThreadState, ToolDefinition } from './types.js';
|
|
14
14
|
export type FeatureToggle = 'on' | 'off';
|
|
15
15
|
export declare abstract class AiChatbotBase extends LitElement {
|
|
16
16
|
protected abstract _messageThreadRef: Ref<AiMessageThreadComponent>;
|
|
@@ -112,6 +112,28 @@ export declare abstract class AiChatbotBase extends LitElement {
|
|
|
112
112
|
startNewChat(): void;
|
|
113
113
|
getMessages(): ChatMessage[];
|
|
114
114
|
setMessages(messages: ChatMessage[]): void;
|
|
115
|
+
/**
|
|
116
|
+
* Inserts or upserts a client-only status message into the thread - never sent to
|
|
117
|
+
* the agent adapter and never included in the conversation history the adapter sees.
|
|
118
|
+
* Use this for host-driven UI feedback (e.g. "Your session expired", upload progress)
|
|
119
|
+
* instead of splicing {@link getMessages}/{@link setMessages} directly.
|
|
120
|
+
*
|
|
121
|
+
* Safe to call at any time, including while a response is streaming - it never
|
|
122
|
+
* finalizes or otherwise touches the in-progress response.
|
|
123
|
+
*
|
|
124
|
+
* @param message - Content and optional kind ('text' | 'info' | 'warning' | 'error' | 'success'),
|
|
125
|
+
* header, and actions. Pass the same `id` on a later call to upsert (replace) a
|
|
126
|
+
* previously added message.
|
|
127
|
+
* @returns The message id (generated if not provided).
|
|
128
|
+
*/
|
|
129
|
+
addClientMessage(message: ClientMessageInput): string;
|
|
130
|
+
/**
|
|
131
|
+
* Removes a client-only message previously added via {@link addClientMessage}.
|
|
132
|
+
* No-ops if no message with that id exists. Safe to call at any time, including
|
|
133
|
+
* while a response is streaming. Removal is never automatic - call this when your
|
|
134
|
+
* host logic determines the message is no longer relevant.
|
|
135
|
+
*/
|
|
136
|
+
removeClientMessage(id: string): void;
|
|
115
137
|
sendMessage(content: string, files?: File[]): Promise<void>;
|
|
116
138
|
abort(): void;
|
|
117
139
|
scrollToBottom({ behavior }?: {
|
|
@@ -125,15 +125,8 @@ class AiChatbotBase extends LitElement {
|
|
|
125
125
|
}
|
|
126
126
|
if (this._hasMessages) {
|
|
127
127
|
const agentName = agent?.name ?? this.titleText;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
role: "system",
|
|
131
|
-
content: `Switched to ${agentName}`,
|
|
132
|
-
timestamp: Date.now(),
|
|
133
|
-
status: "complete",
|
|
134
|
-
clientOnly: true
|
|
135
|
-
};
|
|
136
|
-
this._coreController.addMessage(systemMessage);
|
|
128
|
+
this._coreController.finalizeActiveResponse();
|
|
129
|
+
this._coreController.addClientMessage({ content: `Switched to ${agentName}` });
|
|
137
130
|
}
|
|
138
131
|
}
|
|
139
132
|
}
|
|
@@ -381,6 +374,32 @@ class AiChatbotBase extends LitElement {
|
|
|
381
374
|
setMessages(messages) {
|
|
382
375
|
this._coreController.setMessages(messages);
|
|
383
376
|
}
|
|
377
|
+
/**
|
|
378
|
+
* Inserts or upserts a client-only status message into the thread - never sent to
|
|
379
|
+
* the agent adapter and never included in the conversation history the adapter sees.
|
|
380
|
+
* Use this for host-driven UI feedback (e.g. "Your session expired", upload progress)
|
|
381
|
+
* instead of splicing {@link getMessages}/{@link setMessages} directly.
|
|
382
|
+
*
|
|
383
|
+
* Safe to call at any time, including while a response is streaming - it never
|
|
384
|
+
* finalizes or otherwise touches the in-progress response.
|
|
385
|
+
*
|
|
386
|
+
* @param message - Content and optional kind ('text' | 'info' | 'warning' | 'error' | 'success'),
|
|
387
|
+
* header, and actions. Pass the same `id` on a later call to upsert (replace) a
|
|
388
|
+
* previously added message.
|
|
389
|
+
* @returns The message id (generated if not provided).
|
|
390
|
+
*/
|
|
391
|
+
addClientMessage(message) {
|
|
392
|
+
return this._coreController.addClientMessage(message);
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Removes a client-only message previously added via {@link addClientMessage}.
|
|
396
|
+
* No-ops if no message with that id exists. Safe to call at any time, including
|
|
397
|
+
* while a response is streaming. Removal is never automatic - call this when your
|
|
398
|
+
* host logic determines the message is no longer relevant.
|
|
399
|
+
*/
|
|
400
|
+
removeClientMessage(id) {
|
|
401
|
+
this._coreController.removeClientMessage(id);
|
|
402
|
+
}
|
|
384
403
|
async sendMessage(content, files) {
|
|
385
404
|
if (!this._coreController.adapter) {
|
|
386
405
|
console.warn("No adapter configured.");
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { unsafeCSS, LitElement, nothing, html } from "lit";
|
|
2
2
|
import { property, customElement } from "lit/decorators.js";
|
|
3
3
|
import { createRef, ref } from "lit/directives/ref.js";
|
|
4
|
+
import { shouldShowToolRenderer } from "./utils.mjs";
|
|
4
5
|
import styles from "./ai-chatbot-tool-call.scss.mjs";
|
|
5
6
|
var __defProp = Object.defineProperty;
|
|
6
7
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -20,7 +21,7 @@ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read fr
|
|
|
20
21
|
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);
|
|
21
22
|
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
|
|
22
23
|
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
23
|
-
var _customRendererRef, _renderedElement, _AiChatbotToolCallComponent_instances, shouldRenderCustom_get, dispatchScrollRequest_fn, customRenderer_get;
|
|
24
|
+
var _customRendererRef, _renderedElement, _AiChatbotToolCallComponent_instances, shouldRenderCustom_get, dispatchScrollRequest_fn, customRenderer_get, replaceRenderedElement_fn;
|
|
24
25
|
const AiChatbotToolCallComponentTagName = "forge-ai-chatbot-tool-call";
|
|
25
26
|
let AiChatbotToolCallComponent = class extends LitElement {
|
|
26
27
|
constructor() {
|
|
@@ -31,25 +32,24 @@ let AiChatbotToolCallComponent = class extends LitElement {
|
|
|
31
32
|
}
|
|
32
33
|
updated(changedProperties) {
|
|
33
34
|
super.updated(changedProperties);
|
|
34
|
-
if (changedProperties.has("toolCall")
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
__privateMethod(this, _AiChatbotToolCallComponent_instances, dispatchScrollRequest_fn).call(this);
|
|
47
|
-
} else if (renderer?.render) {
|
|
48
|
-
__privateSet(this, _renderedElement, renderer.render(this.toolCall));
|
|
49
|
-
container.appendChild(__privateGet(this, _renderedElement));
|
|
50
|
-
__privateMethod(this, _AiChatbotToolCallComponent_instances, dispatchScrollRequest_fn).call(this);
|
|
51
|
-
}
|
|
35
|
+
if (!changedProperties.has("toolCall") && !changedProperties.has("toolDefinition")) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const container = __privateGet(this, _customRendererRef).value;
|
|
39
|
+
if (!container || !__privateGet(this, _AiChatbotToolCallComponent_instances, shouldRenderCustom_get)) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const renderer = this.toolDefinition?.renderer;
|
|
43
|
+
if (renderer?.elementTag) {
|
|
44
|
+
if (__privateGet(this, _renderedElement) instanceof HTMLElement && __privateGet(this, _renderedElement).tagName.toLowerCase() === renderer.elementTag) {
|
|
45
|
+
__privateGet(this, _renderedElement).toolCall = this.toolCall;
|
|
46
|
+
return;
|
|
52
47
|
}
|
|
48
|
+
const element = document.createElement(renderer.elementTag);
|
|
49
|
+
element.toolCall = this.toolCall;
|
|
50
|
+
__privateMethod(this, _AiChatbotToolCallComponent_instances, replaceRenderedElement_fn).call(this, container, element);
|
|
51
|
+
} else if (renderer?.render) {
|
|
52
|
+
__privateMethod(this, _AiChatbotToolCallComponent_instances, replaceRenderedElement_fn).call(this, container, renderer.render(this.toolCall));
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
render() {
|
|
@@ -60,14 +60,7 @@ _customRendererRef = /* @__PURE__ */ new WeakMap();
|
|
|
60
60
|
_renderedElement = /* @__PURE__ */ new WeakMap();
|
|
61
61
|
_AiChatbotToolCallComponent_instances = /* @__PURE__ */ new WeakSet();
|
|
62
62
|
shouldRenderCustom_get = function() {
|
|
63
|
-
|
|
64
|
-
if (status === "complete") {
|
|
65
|
-
return true;
|
|
66
|
-
}
|
|
67
|
-
if (this.toolDefinition?.renderOnStart && (status === "parsing" || status === "executing")) {
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
return false;
|
|
63
|
+
return shouldShowToolRenderer(this.toolCall, this.toolDefinition);
|
|
71
64
|
};
|
|
72
65
|
dispatchScrollRequest_fn = function() {
|
|
73
66
|
this.dispatchEvent(
|
|
@@ -87,6 +80,14 @@ customRenderer_get = function() {
|
|
|
87
80
|
}
|
|
88
81
|
return nothing;
|
|
89
82
|
};
|
|
83
|
+
replaceRenderedElement_fn = function(container, element) {
|
|
84
|
+
if (__privateGet(this, _renderedElement) && container.contains(__privateGet(this, _renderedElement))) {
|
|
85
|
+
container.removeChild(__privateGet(this, _renderedElement));
|
|
86
|
+
}
|
|
87
|
+
__privateSet(this, _renderedElement, element);
|
|
88
|
+
container.appendChild(element);
|
|
89
|
+
__privateMethod(this, _AiChatbotToolCallComponent_instances, dispatchScrollRequest_fn).call(this);
|
|
90
|
+
};
|
|
90
91
|
AiChatbotToolCallComponent.styles = unsafeCSS(styles);
|
|
91
92
|
__decorateClass([
|
|
92
93
|
property({ attribute: false })
|
|
@@ -100,6 +100,7 @@ export declare const AiChatbotComponentTagName: keyof HTMLElementTagNameMap;
|
|
|
100
100
|
* @property {FeatureToggle} clearOption - Controls the clear-conversation header action. `'on'` (default) shows it when messages exist; `'off'` hides it entirely.
|
|
101
101
|
* @property {FeatureToggle} exportOption - Controls the export-conversation header action. `'on'` (default) shows it when messages exist; `'off'` hides it entirely.
|
|
102
102
|
* @property {string | null} selectedThreadId - The id of the currently selected conversation thread. Set this to highlight a thread in the conversations panel (e.g. when restoring a conversation loaded from the backend). Updated internally when a thread is selected or a new chat starts.
|
|
103
|
+
* @property {boolean} threadsLoading - When true, shows a loading indicator in the conversations panel's recent chats list while threads are loading (default: false)
|
|
103
104
|
*
|
|
104
105
|
* @cssproperty --forge-ai-chatbot-icon-color - The fill color for the AI icon. Defaults to `tertiary`.
|
|
105
106
|
* @cssproperty --forge-ai-chatbot-suggestion-background - The background color for suggestion buttons. Defaults to `tertiary-container`.
|
|
@@ -142,6 +143,7 @@ export declare class AiChatbotComponent extends AiChatbotBase {
|
|
|
142
143
|
showConversationRename: boolean;
|
|
143
144
|
showConversationDelete: boolean;
|
|
144
145
|
selectedThreadId: string | null;
|
|
146
|
+
threadsLoading: boolean;
|
|
145
147
|
protected _messageThreadRef: Ref<AiMessageThreadComponent>;
|
|
146
148
|
protected _promptRef: Ref<AiPromptComponent>;
|
|
147
149
|
protected _handleInfo(): void;
|
|
@@ -48,6 +48,7 @@ let AiChatbotComponent = class extends AiChatbotBase {
|
|
|
48
48
|
this.showConversationRename = false;
|
|
49
49
|
this.showConversationDelete = false;
|
|
50
50
|
this.selectedThreadId = null;
|
|
51
|
+
this.threadsLoading = false;
|
|
51
52
|
__privateAdd(this, _chatInterfaceRef, createRef());
|
|
52
53
|
this._messageThreadRef = createRef();
|
|
53
54
|
this._promptRef = createRef();
|
|
@@ -214,6 +215,7 @@ let AiChatbotComponent = class extends AiChatbotBase {
|
|
|
214
215
|
${ref(__privateGet(this, _conversationsPanelRef))}
|
|
215
216
|
.recentThreads=${this.recentThreads}
|
|
216
217
|
.selectedThreadId=${this.selectedThreadId}
|
|
218
|
+
?loading=${this.threadsLoading}
|
|
217
219
|
?show-back-button=${true}
|
|
218
220
|
?show-conversation-rename=${this.showConversationRename}
|
|
219
221
|
?show-conversation-delete=${this.showConversationDelete}
|
|
@@ -403,6 +405,9 @@ __decorateClass([
|
|
|
403
405
|
__decorateClass([
|
|
404
406
|
property({ type: String, attribute: "selected-thread-id" })
|
|
405
407
|
], AiChatbotComponent.prototype, "selectedThreadId", 2);
|
|
408
|
+
__decorateClass([
|
|
409
|
+
property({ type: Boolean, attribute: "threads-loading" })
|
|
410
|
+
], AiChatbotComponent.prototype, "threadsLoading", 2);
|
|
406
411
|
AiChatbotComponent = __decorateClass([
|
|
407
412
|
customElement(AiChatbotComponentTagName)
|
|
408
413
|
], AiChatbotComponent);
|
|
@@ -4,7 +4,7 @@ export type { ForgeAiChatbotMessageEventData, ForgeAiChatbotToolCallEventData, F
|
|
|
4
4
|
export { AiChatbotToolCallComponent, AiChatbotToolCallComponentTagName } from './ai-chatbot-tool-call.js';
|
|
5
5
|
export type { Suggestion } from '../ai-suggestions/index.js';
|
|
6
6
|
export { AgentAdapter, type MessageStartEvent, type MessageDeltaEvent, type MessageEndEvent, type ToolCallEvent, type ToolCallStartEvent, type ToolCallArgsEvent, type ToolCallEndEvent, type FileUploadEvent, type FileRemoveEvent, type AdapterState, type ErrorEvent, type CustomAgentEvent, type RawAgentEvent, type RunStartedAgentEvent, type StepStartedAgentEvent, type StepFinishedAgentEvent, type StateSnapshotAgentEvent, type StateDeltaAgentEvent, type ActivitySnapshotAgentEvent, type ActivityDeltaAgentEvent } from './agent-adapter.js';
|
|
7
|
-
export type { Agent, ChatMessage, ContextItem, ToolDefinition, ToolCall, ToolRenderer, IToolRenderer, HandlerContext, ToolStartContext, ToolDeltaContext, ToolEndContext, FileAttachment, UploadedFileMetadata, FileUploadCallbacks, FileUploadHandler, FileRemoveCallbacks, ThreadState, FeedbackType, ResponseFeedback, MessageItem, ForgeAiChatbotContextRemoveEventData, ForgeAiChatbotConversationSelectEventData, ForgeAiChatbotConversationSearchEventData, ForgeAiChatbotConversationLoadMoreEventData, ForgeAiChatbotConversationRenameEventData, ForgeAiChatbotConversationDeleteEventData } from './types.js';
|
|
7
|
+
export type { Agent, ChatMessage, ClientMessageAction, ClientMessageInput, ClientMessageKind, ContextItem, ToolDefinition, ToolCall, ToolRenderer, IToolRenderer, HandlerContext, ToolStartContext, ToolDeltaContext, ToolEndContext, FileAttachment, UploadedFileMetadata, FileUploadCallbacks, FileUploadHandler, FileRemoveCallbacks, ThreadState, FeedbackType, ResponseFeedback, MessageItem, ForgeAiChatbotContextRemoveEventData, ForgeAiChatbotConversationSelectEventData, ForgeAiChatbotConversationSearchEventData, ForgeAiChatbotConversationLoadMoreEventData, ForgeAiChatbotConversationRenameEventData, ForgeAiChatbotConversationDeleteEventData } from './types.js';
|
|
8
8
|
export * from './ag-ui-adapter.js';
|
|
9
9
|
export * from './http-agent-with-credentials.js';
|
|
10
10
|
export * from './event-emitter.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactiveController, ReactiveControllerHost } from 'lit';
|
|
2
|
-
import { ChatMessage, MessageItem, ToolCall, AssistantResponse, ResponseFeedback } from './types.js';
|
|
2
|
+
import { ChatMessage, ClientMessageInput, MessageItem, ToolCall, AssistantResponse, ResponseFeedback } from './types.js';
|
|
3
3
|
import { MessageStartEvent, MessageDeltaEvent, MessageEndEvent, ToolCallStartEvent, ToolCallArgsEvent, ToolCallEndEvent, ToolResultEvent } from './agent-adapter.js';
|
|
4
4
|
export interface MessageStateControllerConfig {
|
|
5
5
|
onThreadSettled?: () => void;
|
|
@@ -28,6 +28,8 @@ export declare class MessageStateController implements ReactiveController {
|
|
|
28
28
|
getToolCall(id: string): ToolCall | undefined;
|
|
29
29
|
getActiveResponse(): AssistantResponse | null;
|
|
30
30
|
startResponse(): AssistantResponse;
|
|
31
|
+
markResponseThinking(): void;
|
|
32
|
+
clearResponseThinking(): void;
|
|
31
33
|
addTextToResponse(messageId: string, content: string, event?: MessageStartEvent): void;
|
|
32
34
|
appendTextDelta(messageId: string, delta: string, event?: MessageDeltaEvent): void;
|
|
33
35
|
markTextComplete(messageId: string, event?: MessageEndEvent): void;
|
|
@@ -41,6 +43,14 @@ export declare class MessageStateController implements ReactiveController {
|
|
|
41
43
|
addMessageItem(item: MessageItem): void;
|
|
42
44
|
addMessage(message: ChatMessage, event?: MessageStartEvent): void;
|
|
43
45
|
getMessage(id: string): ChatMessage | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Inserts or upserts a client-only message, never touching `_activeResponse`.
|
|
48
|
+
* Unlike {@link addMessage}, this never finalizes an in-progress response -
|
|
49
|
+
* safe to call while a real reply is streaming.
|
|
50
|
+
*/
|
|
51
|
+
addClientMessage(message: ClientMessageInput): string;
|
|
52
|
+
/** Removes a client message by id. No-ops if not found. Safe to call at any time. */
|
|
53
|
+
removeClientMessage(id: string): void;
|
|
44
54
|
updateMessageStatus(id: string, status: ChatMessage['status'], event?: MessageEndEvent): void;
|
|
45
55
|
updateMessageContent(id: string, content: string): void;
|
|
46
56
|
removeMessageItem(index: number): void;
|
|
@@ -42,8 +42,25 @@ class MessageStateController {
|
|
|
42
42
|
this.#notifyStateChange();
|
|
43
43
|
return response;
|
|
44
44
|
}
|
|
45
|
+
markResponseThinking() {
|
|
46
|
+
if (!this._activeResponse || this._activeResponse.isThinking) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this._activeResponse.isThinking = true;
|
|
50
|
+
this.#updateResponseInItems();
|
|
51
|
+
this.#notifyStateChange();
|
|
52
|
+
}
|
|
53
|
+
clearResponseThinking() {
|
|
54
|
+
if (!this._activeResponse || !this._activeResponse.isThinking) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this._activeResponse.isThinking = false;
|
|
58
|
+
this.#updateResponseInItems();
|
|
59
|
+
this.#notifyStateChange();
|
|
60
|
+
}
|
|
45
61
|
addTextToResponse(messageId, content, event) {
|
|
46
62
|
const response = this._activeResponse ?? this.startResponse();
|
|
63
|
+
response.isThinking = false;
|
|
47
64
|
const lastChild = response.children[response.children.length - 1];
|
|
48
65
|
if (lastChild?.type === "text" && lastChild.messageId === messageId) {
|
|
49
66
|
lastChild.content = content;
|
|
@@ -64,6 +81,7 @@ class MessageStateController {
|
|
|
64
81
|
}
|
|
65
82
|
appendTextDelta(messageId, delta, event) {
|
|
66
83
|
const response = this._activeResponse ?? this.startResponse();
|
|
84
|
+
response.isThinking = false;
|
|
67
85
|
const lastChild = response.children[response.children.length - 1];
|
|
68
86
|
if (lastChild?.type === "text" && lastChild.messageId === messageId) {
|
|
69
87
|
lastChild.content += delta;
|
|
@@ -102,6 +120,7 @@ class MessageStateController {
|
|
|
102
120
|
}
|
|
103
121
|
addToolCallToResponse(toolCall, event) {
|
|
104
122
|
const response = this._activeResponse ?? this.startResponse();
|
|
123
|
+
response.isThinking = false;
|
|
105
124
|
const toolCallWithTimestamp = { ...toolCall, startTimestamp: Date.now() };
|
|
106
125
|
this._toolCalls.set(toolCall.id, toolCallWithTimestamp);
|
|
107
126
|
response.children.push({ type: "toolCall", data: toolCallWithTimestamp });
|
|
@@ -167,7 +186,7 @@ class MessageStateController {
|
|
|
167
186
|
completeToolCallInResponse(toolCallId, result, event) {
|
|
168
187
|
this.updateToolCallInResponse(toolCallId, {
|
|
169
188
|
result,
|
|
170
|
-
status: "complete"
|
|
189
|
+
status: event?.isError === true ? "error" : "complete"
|
|
171
190
|
});
|
|
172
191
|
if (event) {
|
|
173
192
|
const streamEvent = {
|
|
@@ -255,6 +274,40 @@ class MessageStateController {
|
|
|
255
274
|
const item = this._messageItems.find((i) => i.type === "message" && i.data.id === id);
|
|
256
275
|
return item?.type === "message" ? item.data : void 0;
|
|
257
276
|
}
|
|
277
|
+
/**
|
|
278
|
+
* Inserts or upserts a client-only message, never touching `_activeResponse`.
|
|
279
|
+
* Unlike {@link addMessage}, this never finalizes an in-progress response -
|
|
280
|
+
* safe to call while a real reply is streaming.
|
|
281
|
+
*/
|
|
282
|
+
addClientMessage(message) {
|
|
283
|
+
const id = message.id ?? generateId();
|
|
284
|
+
const chatMessage = {
|
|
285
|
+
id,
|
|
286
|
+
role: "system",
|
|
287
|
+
content: message.content,
|
|
288
|
+
timestamp: Date.now(),
|
|
289
|
+
status: "complete",
|
|
290
|
+
clientOnly: true,
|
|
291
|
+
kind: message.kind,
|
|
292
|
+
header: message.header,
|
|
293
|
+
actions: message.actions
|
|
294
|
+
};
|
|
295
|
+
this._messageItems = [
|
|
296
|
+
...this._messageItems.filter((item) => !(item.type === "message" && item.data.id === id)),
|
|
297
|
+
{ type: "message", data: chatMessage }
|
|
298
|
+
];
|
|
299
|
+
this.#notifyStateChange();
|
|
300
|
+
return id;
|
|
301
|
+
}
|
|
302
|
+
/** Removes a client message by id. No-ops if not found. Safe to call at any time. */
|
|
303
|
+
removeClientMessage(id) {
|
|
304
|
+
const hasMessage = this._messageItems.some((item) => item.type === "message" && item.data.id === id);
|
|
305
|
+
if (!hasMessage) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
this._messageItems = this._messageItems.filter((item) => !(item.type === "message" && item.data.id === id));
|
|
309
|
+
this.#notifyStateChange();
|
|
310
|
+
}
|
|
258
311
|
updateMessageStatus(id, status, event) {
|
|
259
312
|
this._messageItems = this._messageItems.map((item) => {
|
|
260
313
|
if (item.type === "message" && item.data.id === id) {
|
|
@@ -132,6 +132,7 @@ export interface ToolResultEvent {
|
|
|
132
132
|
toolCallId: string;
|
|
133
133
|
result: unknown;
|
|
134
134
|
message: ChatMessage;
|
|
135
|
+
isError?: boolean;
|
|
135
136
|
}
|
|
136
137
|
export type StreamEvent = {
|
|
137
138
|
type: 'message-start';
|
|
@@ -181,6 +182,31 @@ export interface ChatMessage {
|
|
|
181
182
|
feedback?: ResponseFeedback;
|
|
182
183
|
children?: ResponseItem[];
|
|
183
184
|
clientOnly?: boolean;
|
|
185
|
+
kind?: ClientMessageKind;
|
|
186
|
+
header?: string;
|
|
187
|
+
actions?: ClientMessageAction[];
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Visual treatment for a client-only message. `text` matches the plain,
|
|
191
|
+
* italic system-message style; the others render as a Forge inline-message
|
|
192
|
+
* banner with a matching icon.
|
|
193
|
+
*/
|
|
194
|
+
export type ClientMessageKind = 'text' | 'info' | 'warning' | 'error' | 'success';
|
|
195
|
+
export interface ClientMessageAction {
|
|
196
|
+
id: string;
|
|
197
|
+
label: string;
|
|
198
|
+
onClick: () => void;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Input to {@link AiChatbotBase.addClientMessage}. Pass the same `id` on a
|
|
202
|
+
* later call to upsert (replace) a previously added client message.
|
|
203
|
+
*/
|
|
204
|
+
export interface ClientMessageInput {
|
|
205
|
+
id?: string;
|
|
206
|
+
content: string;
|
|
207
|
+
kind?: ClientMessageKind;
|
|
208
|
+
header?: string;
|
|
209
|
+
actions?: ClientMessageAction[];
|
|
184
210
|
}
|
|
185
211
|
export interface ToolCall<TArgs = Record<string, unknown>> {
|
|
186
212
|
id: string;
|
|
@@ -216,6 +242,7 @@ export interface AssistantResponse {
|
|
|
216
242
|
timestamp: number;
|
|
217
243
|
eventStream?: StreamEvent[];
|
|
218
244
|
feedback?: ResponseFeedback;
|
|
245
|
+
isThinking?: boolean;
|
|
219
246
|
}
|
|
220
247
|
export interface FileAttachment {
|
|
221
248
|
id: string;
|
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
import { ChatMessage, ContextItem, FileAttachment, ToolCall } from './types.js';
|
|
1
|
+
import { ChatMessage, ContextItem, FileAttachment, ToolCall, ToolDefinition } from './types.js';
|
|
2
2
|
export { renderMarkdown, renderInlineMarkdown, type RenderMarkdownOptions } from '../utils/markdown.js';
|
|
3
|
+
/**
|
|
4
|
+
* Whether a tool call's renderer should currently be shown: once the call is complete,
|
|
5
|
+
* or immediately (while still parsing/executing) if the tool opted into `renderOnStart`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function shouldShowToolRenderer(toolCall: ToolCall, toolDefinition?: ToolDefinition): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Whether a tool call should be treated as settled for activity indicators (thinking
|
|
10
|
+
* indicator, tool-call summary spinner): either genuinely finished, or already showing
|
|
11
|
+
* its own renderer to the user, which makes a redundant "running" spinner misleading.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isToolCallSettled(toolCall: ToolCall, toolDefinition?: ToolDefinition): boolean;
|
|
3
14
|
/**
|
|
4
15
|
* Generates a unique ID using UUID v4
|
|
5
16
|
*/
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import { v4 } from "uuid";
|
|
2
2
|
import { renderInlineMarkdown, renderMarkdown } from "../utils/markdown.mjs";
|
|
3
|
+
function shouldShowToolRenderer(toolCall, toolDefinition) {
|
|
4
|
+
if (!toolDefinition?.renderer) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
if (toolCall.status === "complete") {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
return !!toolDefinition.renderOnStart && (toolCall.status === "parsing" || toolCall.status === "executing");
|
|
11
|
+
}
|
|
12
|
+
function isToolCallSettled(toolCall, toolDefinition) {
|
|
13
|
+
return toolCall.status === "complete" || toolCall.status === "error" || shouldShowToolRenderer(toolCall, toolDefinition);
|
|
14
|
+
}
|
|
3
15
|
function generateId() {
|
|
4
16
|
return v4();
|
|
5
17
|
}
|
|
@@ -89,6 +101,8 @@ export {
|
|
|
89
101
|
formatChatTranscript,
|
|
90
102
|
formatToolCallForExport,
|
|
91
103
|
generateId,
|
|
104
|
+
isToolCallSettled,
|
|
92
105
|
renderInlineMarkdown,
|
|
93
|
-
renderMarkdown
|
|
106
|
+
renderMarkdown,
|
|
107
|
+
shouldShowToolRenderer
|
|
94
108
|
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { LitElement, TemplateResult, CSSResult } from 'lit';
|
|
2
|
+
import { ClientMessageAction, ClientMessageKind } from '../ai-chatbot/types.js';
|
|
3
|
+
declare global {
|
|
4
|
+
interface HTMLElementTagNameMap {
|
|
5
|
+
'forge-ai-client-message': AiClientMessageComponent;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export declare const AiClientMessageComponentTagName: keyof HTMLElementTagNameMap;
|
|
9
|
+
/**
|
|
10
|
+
* @tag forge-ai-client-message
|
|
11
|
+
*
|
|
12
|
+
* @summary Renders a client-only status message (never sent to the agent adapter) as
|
|
13
|
+
* either plain system-message text or a Forge inline-message banner, depending on `kind`.
|
|
14
|
+
*
|
|
15
|
+
* @description
|
|
16
|
+
* Used internally by `forge-ai-message-thread` to render messages added via
|
|
17
|
+
* `AiChatbotBase.addClientMessage()`. Not intended to be used standalone outside a
|
|
18
|
+
* message thread, but exported for direct Storybook/testing use.
|
|
19
|
+
*/
|
|
20
|
+
export declare class AiClientMessageComponent extends LitElement {
|
|
21
|
+
#private;
|
|
22
|
+
static styles: CSSResult;
|
|
23
|
+
kind: ClientMessageKind;
|
|
24
|
+
header?: string;
|
|
25
|
+
content: string;
|
|
26
|
+
actions: ClientMessageAction[];
|
|
27
|
+
render(): TemplateResult;
|
|
28
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { unsafeCSS, LitElement, html, nothing } from "lit";
|
|
2
|
+
import { property, customElement } from "lit/decorators.js";
|
|
3
|
+
import { when } from "lit/directives/when.js";
|
|
4
|
+
import styles from "./ai-client-message.scss.mjs";
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __typeError = (msg) => {
|
|
8
|
+
throw TypeError(msg);
|
|
9
|
+
};
|
|
10
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
11
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
12
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
13
|
+
if (decorator = decorators[i])
|
|
14
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
15
|
+
if (kind && result) __defProp(target, key, result);
|
|
16
|
+
return result;
|
|
17
|
+
};
|
|
18
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
19
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
20
|
+
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);
|
|
21
|
+
var _AiClientMessageComponent_instances, icon_get, actions_get;
|
|
22
|
+
const AiClientMessageComponentTagName = "forge-ai-client-message";
|
|
23
|
+
const KIND_ICON_PATHS = {
|
|
24
|
+
info: "M11 7h2v2h-2zm0 4h2v6h-2zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2m0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8",
|
|
25
|
+
warning: "M12 2 1 21h22zm0 3.99L19.53 19H4.47zM11 10v4h2v-4zm0 6v2h2v-2z",
|
|
26
|
+
error: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2m1 15h-2v-2h2zm0-4h-2V7h2z",
|
|
27
|
+
success: "M21 7 9 19l-5.5-5.5 1.41-1.41L9 16.17 19.59 5.59z"
|
|
28
|
+
};
|
|
29
|
+
let AiClientMessageComponent = class extends LitElement {
|
|
30
|
+
constructor() {
|
|
31
|
+
super(...arguments);
|
|
32
|
+
__privateAdd(this, _AiClientMessageComponent_instances);
|
|
33
|
+
this.kind = "text";
|
|
34
|
+
this.content = "";
|
|
35
|
+
this.actions = [];
|
|
36
|
+
}
|
|
37
|
+
render() {
|
|
38
|
+
if (this.kind === "text") {
|
|
39
|
+
return html`
|
|
40
|
+
<div class="client-message--text">
|
|
41
|
+
<span>${this.content}</span>
|
|
42
|
+
${__privateGet(this, _AiClientMessageComponent_instances, actions_get)}
|
|
43
|
+
</div>
|
|
44
|
+
`;
|
|
45
|
+
}
|
|
46
|
+
return html`
|
|
47
|
+
<div class="forge-inline-message">
|
|
48
|
+
${__privateGet(this, _AiClientMessageComponent_instances, icon_get)} ${when(this.header, () => html`<div class="forge-inline-message__title">${this.header}</div>`)}
|
|
49
|
+
<div class="forge-inline-message__content">
|
|
50
|
+
<span>${this.content}</span>
|
|
51
|
+
${__privateGet(this, _AiClientMessageComponent_instances, actions_get)}
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
`;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
_AiClientMessageComponent_instances = /* @__PURE__ */ new WeakSet();
|
|
58
|
+
icon_get = function() {
|
|
59
|
+
if (this.kind === "text") {
|
|
60
|
+
return nothing;
|
|
61
|
+
}
|
|
62
|
+
return html`
|
|
63
|
+
<svg
|
|
64
|
+
class="forge-inline-message__icon"
|
|
65
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
66
|
+
viewBox="0 0 24 24"
|
|
67
|
+
height="24"
|
|
68
|
+
width="24"
|
|
69
|
+
fill="currentColor"
|
|
70
|
+
aria-hidden="true">
|
|
71
|
+
<path fill="none" d="M0 0h24v24H0z" />
|
|
72
|
+
<path d="${KIND_ICON_PATHS[this.kind]}" />
|
|
73
|
+
</svg>
|
|
74
|
+
`;
|
|
75
|
+
};
|
|
76
|
+
actions_get = function() {
|
|
77
|
+
return when(
|
|
78
|
+
this.actions.length > 0,
|
|
79
|
+
() => html`
|
|
80
|
+
<div class="client-message__actions">
|
|
81
|
+
${this.actions.map(
|
|
82
|
+
(action) => html`<button
|
|
83
|
+
type="button"
|
|
84
|
+
class="forge-button forge-button--outlined forge-button--dense"
|
|
85
|
+
@click=${() => action.onClick()}>
|
|
86
|
+
${action.label}
|
|
87
|
+
</button>`
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
90
|
+
`
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
AiClientMessageComponent.styles = unsafeCSS(styles);
|
|
94
|
+
__decorateClass([
|
|
95
|
+
property({ type: String, reflect: true })
|
|
96
|
+
], AiClientMessageComponent.prototype, "kind", 2);
|
|
97
|
+
__decorateClass([
|
|
98
|
+
property({ type: String })
|
|
99
|
+
], AiClientMessageComponent.prototype, "header", 2);
|
|
100
|
+
__decorateClass([
|
|
101
|
+
property({ type: String })
|
|
102
|
+
], AiClientMessageComponent.prototype, "content", 2);
|
|
103
|
+
__decorateClass([
|
|
104
|
+
property({ attribute: false })
|
|
105
|
+
], AiClientMessageComponent.prototype, "actions", 2);
|
|
106
|
+
AiClientMessageComponent = __decorateClass([
|
|
107
|
+
customElement(AiClientMessageComponentTagName)
|
|
108
|
+
], AiClientMessageComponent);
|
|
109
|
+
export {
|
|
110
|
+
AiClientMessageComponent,
|
|
111
|
+
AiClientMessageComponentTagName
|
|
112
|
+
};
|