@tylertech/forge-ai 0.10.1 → 0.11.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 +5456 -3863
- package/dist/ai-chatbot/ai-chatbot-base.d.ts +92 -0
- package/dist/ai-chatbot/ai-chatbot-base.mjs +467 -0
- package/dist/ai-chatbot/ai-chatbot.d.ts +26 -75
- package/dist/ai-chatbot/ai-chatbot.mjs +51 -808
- package/dist/ai-chatbot/index.d.ts +3 -2
- package/dist/ai-chatbot/index.mjs +2 -0
- package/dist/ai-chatbot/message-state-controller.mjs +1 -1
- package/dist/ai-chatbot-launcher/ai-chatbot-launcher.d.ts +71 -0
- package/dist/ai-chatbot-launcher/ai-chatbot-launcher.mjs +288 -0
- package/dist/ai-chatbot-launcher/ai-chatbot-launcher.scss.mjs +4 -0
- package/dist/ai-chatbot-launcher/index.d.ts +1 -0
- package/dist/ai-chatbot-launcher/index.mjs +5 -0
- package/dist/ai-embedded-chat/ai-embedded-chat.mjs +1 -0
- package/dist/ai-message-thread/ai-message-thread.d.ts +2 -0
- package/dist/ai-message-thread/ai-message-thread.mjs +1 -1
- package/dist/ai-message-thread/ai-message-thread.scss.mjs +1 -1
- package/dist/ai-prompt/ai-prompt.scss.mjs +1 -1
- package/dist/core/chatbot-core-controller.d.ts +51 -0
- package/dist/core/chatbot-core-controller.mjs +400 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.mjs +2 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +5 -0
- package/dist/tools/ai-data-table/ai-data-table-definition.mjs +1 -0
- package/package.json +24 -24
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { nothing, LitElement, PropertyValues, TemplateResult } from 'lit';
|
|
2
|
+
import { Ref } from 'lit/directives/ref.js';
|
|
3
|
+
import { AgentInfo } from '../ai-agent-info';
|
|
4
|
+
import { ForgeAiChatHeaderAgentChangeEventData } from '../ai-chat-header';
|
|
5
|
+
import { AiMessageThreadComponent, ForgeAiMessageThreadThumbsEventData } from '../ai-message-thread';
|
|
6
|
+
import { ForgeAiAttachmentRemoveEventData } from '../ai-attachment';
|
|
7
|
+
import { ForgeAiFilePickerChangeEventData, ForgeAiFilePickerErrorEventData } from '../ai-file-picker';
|
|
8
|
+
import { AiPromptComponent, ForgeAiPromptCommandEventData, ForgeAiPromptSendEventData } from '../ai-prompt';
|
|
9
|
+
import { ForgeAiSuggestionsEventData, Suggestion } from '../ai-suggestions';
|
|
10
|
+
import { ForgeAiVoiceInputResultEvent } from '../ai-voice-input';
|
|
11
|
+
import { AgentAdapter } from './agent-adapter.js';
|
|
12
|
+
import { ChatbotCoreController } from '../core/chatbot-core-controller.js';
|
|
13
|
+
import { Agent, ChatMessage, HeadingLevel, MessageItem, SlashCommand, ThreadState, ToolCall, ToolDefinition } from './types.js';
|
|
14
|
+
export type FeatureToggle = 'on' | 'off';
|
|
15
|
+
export declare abstract class AiChatbotBase extends LitElement {
|
|
16
|
+
protected abstract _messageThreadRef: Ref<AiMessageThreadComponent>;
|
|
17
|
+
protected abstract _promptRef: Ref<AiPromptComponent>;
|
|
18
|
+
adapter?: AgentAdapter;
|
|
19
|
+
fileUpload: FeatureToggle;
|
|
20
|
+
voiceInput: FeatureToggle;
|
|
21
|
+
placeholder: string;
|
|
22
|
+
suggestions?: Suggestion[];
|
|
23
|
+
enableReactions: boolean;
|
|
24
|
+
titleText: string;
|
|
25
|
+
headingLevel: HeadingLevel;
|
|
26
|
+
debugMode: boolean;
|
|
27
|
+
disclaimerText: string | null | undefined;
|
|
28
|
+
debugCommand: FeatureToggle;
|
|
29
|
+
agentInfo?: AgentInfo;
|
|
30
|
+
agents: Agent[];
|
|
31
|
+
selectedAgentId?: string;
|
|
32
|
+
protected _coreController: ChatbotCoreController;
|
|
33
|
+
protected get _isStreaming(): boolean;
|
|
34
|
+
protected get _isUploading(): boolean;
|
|
35
|
+
protected get _tools(): Map<string, ToolDefinition>;
|
|
36
|
+
protected get _messageItems(): MessageItem[];
|
|
37
|
+
protected get _hasMessages(): boolean;
|
|
38
|
+
connectedCallback(): void;
|
|
39
|
+
willUpdate(changedProperties: PropertyValues<this>): void;
|
|
40
|
+
protected _onConnected(): void;
|
|
41
|
+
protected get _slashCommands(): SlashCommand[];
|
|
42
|
+
protected _handleSlashCommand(evt: CustomEvent<ForgeAiPromptCommandEventData>): void;
|
|
43
|
+
protected _handleInfo(): void;
|
|
44
|
+
protected _handleAgentChange(event: CustomEvent<ForgeAiChatHeaderAgentChangeEventData>): void;
|
|
45
|
+
protected _handleDebugToggle(): void;
|
|
46
|
+
protected _handleAttachmentRemove(evt: CustomEvent<ForgeAiAttachmentRemoveEventData>): void;
|
|
47
|
+
protected get _sessionFilesTemplate(): TemplateResult | typeof nothing;
|
|
48
|
+
protected _handleSend(evt: CustomEvent<ForgeAiPromptSendEventData>): Promise<void>;
|
|
49
|
+
protected _handleStop(): void;
|
|
50
|
+
protected _handleCancel(): void;
|
|
51
|
+
protected _handleCopy(evt: CustomEvent<{
|
|
52
|
+
messageId: string;
|
|
53
|
+
}>): Promise<void>;
|
|
54
|
+
protected _handleUserCopy(evt: CustomEvent<{
|
|
55
|
+
messageId: string;
|
|
56
|
+
}>): Promise<void>;
|
|
57
|
+
protected _handleUserResend(evt: CustomEvent<{
|
|
58
|
+
messageId: string;
|
|
59
|
+
}>): void;
|
|
60
|
+
protected _handleUserEdit(evt: CustomEvent<{
|
|
61
|
+
messageId: string;
|
|
62
|
+
content: string;
|
|
63
|
+
}>): void;
|
|
64
|
+
protected _handleResend(evt: CustomEvent<{
|
|
65
|
+
messageId: string;
|
|
66
|
+
}>): void;
|
|
67
|
+
protected _handleFeedback(evt: CustomEvent<ForgeAiMessageThreadThumbsEventData>, type: 'positive' | 'negative'): void;
|
|
68
|
+
protected _handleThumbsUp(evt: CustomEvent<ForgeAiMessageThreadThumbsEventData>): void;
|
|
69
|
+
protected _handleThumbsDown(evt: CustomEvent<ForgeAiMessageThreadThumbsEventData>): void;
|
|
70
|
+
protected _handleFileSelect(evt: CustomEvent<ForgeAiFilePickerChangeEventData>): void;
|
|
71
|
+
protected _handleFileError(evt: CustomEvent<ForgeAiFilePickerErrorEventData>): void;
|
|
72
|
+
protected _handleSuggestionSelect(evt: CustomEvent<ForgeAiSuggestionsEventData>): Promise<void>;
|
|
73
|
+
protected _handleVoiceInputResult(evt: CustomEvent<ForgeAiVoiceInputResultEvent>): void;
|
|
74
|
+
clearMessages(): boolean;
|
|
75
|
+
getMessages(): ChatMessage[];
|
|
76
|
+
setMessages(messages: ChatMessage[]): void;
|
|
77
|
+
sendMessage(content: string, files?: File[]): Promise<void>;
|
|
78
|
+
abort(): void;
|
|
79
|
+
scrollToBottom({ behavior }?: {
|
|
80
|
+
behavior?: ScrollBehavior;
|
|
81
|
+
}): Promise<void>;
|
|
82
|
+
getThreadState(): ThreadState;
|
|
83
|
+
getSelectedAgent(): Agent | undefined;
|
|
84
|
+
setThreadState(threadState: ThreadState): Promise<void>;
|
|
85
|
+
protected _dispatchHostEvent(config: {
|
|
86
|
+
type: string;
|
|
87
|
+
detail?: unknown;
|
|
88
|
+
cancelable?: boolean;
|
|
89
|
+
}): CustomEvent;
|
|
90
|
+
protected _formatToolCallForExport(toolCall: ToolCall): string;
|
|
91
|
+
protected _handleExport(): void;
|
|
92
|
+
}
|
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import { LitElement, nothing, html } from "lit";
|
|
2
|
+
import { property } from "lit/decorators.js";
|
|
3
|
+
import { ChatbotCoreController } from "../core/chatbot-core-controller.mjs";
|
|
4
|
+
import { generateId, downloadFile } from "./utils.mjs";
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
7
|
+
var result = void 0;
|
|
8
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
9
|
+
if (decorator = decorators[i])
|
|
10
|
+
result = decorator(target, key, result) || result;
|
|
11
|
+
if (result) __defProp(target, key, result);
|
|
12
|
+
return result;
|
|
13
|
+
};
|
|
14
|
+
class AiChatbotBase extends LitElement {
|
|
15
|
+
constructor() {
|
|
16
|
+
super(...arguments);
|
|
17
|
+
this.fileUpload = "off";
|
|
18
|
+
this.voiceInput = "on";
|
|
19
|
+
this.placeholder = "Ask a question...";
|
|
20
|
+
this.enableReactions = false;
|
|
21
|
+
this.titleText = "AI Assistant";
|
|
22
|
+
this.headingLevel = 2;
|
|
23
|
+
this.debugMode = false;
|
|
24
|
+
this.disclaimerText = "AI can make mistakes. Always verify responses.";
|
|
25
|
+
this.debugCommand = "on";
|
|
26
|
+
this.agents = [];
|
|
27
|
+
}
|
|
28
|
+
get _isStreaming() {
|
|
29
|
+
return this._coreController?.isStreaming ?? false;
|
|
30
|
+
}
|
|
31
|
+
get _isUploading() {
|
|
32
|
+
return this._coreController?.isUploading ?? false;
|
|
33
|
+
}
|
|
34
|
+
get _tools() {
|
|
35
|
+
return this._coreController?.tools ?? /* @__PURE__ */ new Map();
|
|
36
|
+
}
|
|
37
|
+
get _messageItems() {
|
|
38
|
+
return this._coreController?.messageItems ?? [];
|
|
39
|
+
}
|
|
40
|
+
get _hasMessages() {
|
|
41
|
+
return this._coreController?.hasMessages ?? false;
|
|
42
|
+
}
|
|
43
|
+
connectedCallback() {
|
|
44
|
+
super.connectedCallback();
|
|
45
|
+
this._coreController = new ChatbotCoreController(this, {
|
|
46
|
+
callbacks: {
|
|
47
|
+
onRequestUpdate: () => this.requestUpdate(),
|
|
48
|
+
onScrollToBottom: () => this.scrollToBottom(),
|
|
49
|
+
onDispatchEvent: (type, detail, cancelable) => this._dispatchHostEvent({ type, detail, cancelable })
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
if (this.adapter) {
|
|
53
|
+
this._coreController.adapter = this.adapter;
|
|
54
|
+
}
|
|
55
|
+
this._onConnected();
|
|
56
|
+
}
|
|
57
|
+
willUpdate(changedProperties) {
|
|
58
|
+
if (changedProperties.has("adapter") && this._coreController) {
|
|
59
|
+
this._coreController.adapter = this.adapter;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
_onConnected() {
|
|
63
|
+
}
|
|
64
|
+
get _slashCommands() {
|
|
65
|
+
const commands = [];
|
|
66
|
+
if (this._hasMessages) {
|
|
67
|
+
commands.push({ id: "clear", name: "Clear", group: "Conversation" });
|
|
68
|
+
commands.push({ id: "export", name: "Export", group: "Conversation" });
|
|
69
|
+
}
|
|
70
|
+
commands.push({ id: "info", name: "Info", group: "Help" });
|
|
71
|
+
if (this.debugCommand === "on") {
|
|
72
|
+
commands.push({
|
|
73
|
+
id: "debug",
|
|
74
|
+
name: `${this.debugMode ? "Disable debug mode" : "Enable debug mode"}`,
|
|
75
|
+
group: "Help"
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return commands;
|
|
79
|
+
}
|
|
80
|
+
_handleSlashCommand(evt) {
|
|
81
|
+
const commandId = evt.detail.commandId;
|
|
82
|
+
switch (commandId) {
|
|
83
|
+
case "clear":
|
|
84
|
+
this.clearMessages();
|
|
85
|
+
break;
|
|
86
|
+
case "export":
|
|
87
|
+
this._handleExport();
|
|
88
|
+
break;
|
|
89
|
+
case "info":
|
|
90
|
+
this._handleInfo();
|
|
91
|
+
break;
|
|
92
|
+
case "debug":
|
|
93
|
+
this._handleDebugToggle();
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
_handleInfo() {
|
|
98
|
+
this._dispatchHostEvent({ type: "forge-ai-chatbot-info" });
|
|
99
|
+
}
|
|
100
|
+
_handleAgentChange(event) {
|
|
101
|
+
const { agent, previousAgentId } = event.detail;
|
|
102
|
+
const changeEvt = this._dispatchHostEvent({
|
|
103
|
+
type: "forge-ai-chatbot-agent-change",
|
|
104
|
+
detail: { agent, previousAgentId }
|
|
105
|
+
});
|
|
106
|
+
if (!changeEvt.defaultPrevented) {
|
|
107
|
+
this.selectedAgentId = agent?.id;
|
|
108
|
+
const adapter = this._coreController.adapter;
|
|
109
|
+
if (adapter) {
|
|
110
|
+
adapter.threadId = generateId();
|
|
111
|
+
}
|
|
112
|
+
if (this._hasMessages) {
|
|
113
|
+
const agentName = agent?.name ?? this.titleText;
|
|
114
|
+
const systemMessage = {
|
|
115
|
+
id: generateId(),
|
|
116
|
+
role: "system",
|
|
117
|
+
content: `Switched to ${agentName}`,
|
|
118
|
+
timestamp: Date.now(),
|
|
119
|
+
status: "complete",
|
|
120
|
+
clientOnly: true
|
|
121
|
+
};
|
|
122
|
+
this._coreController.addMessage(systemMessage);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
_handleDebugToggle() {
|
|
127
|
+
this.debugMode = !this.debugMode;
|
|
128
|
+
}
|
|
129
|
+
_handleAttachmentRemove(evt) {
|
|
130
|
+
const { filename } = evt.detail;
|
|
131
|
+
const pendingAttachment = this._coreController.pendingAttachments.find((a) => a.filename === filename);
|
|
132
|
+
if (pendingAttachment) {
|
|
133
|
+
this._coreController.abortFileUpload(pendingAttachment.id);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const completedAttachment = this._coreController.completedAttachments.find((a) => a.filename === filename);
|
|
137
|
+
if (completedAttachment) {
|
|
138
|
+
this._coreController.removeCompletedAttachment(completedAttachment.id);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
get _sessionFilesTemplate() {
|
|
142
|
+
const completed = this._coreController?.completedAttachments ?? [];
|
|
143
|
+
const uploading = this._coreController?.pendingAttachments ?? [];
|
|
144
|
+
const allFiles = [...uploading, ...completed];
|
|
145
|
+
if (allFiles.length === 0) {
|
|
146
|
+
return nothing;
|
|
147
|
+
}
|
|
148
|
+
return html`
|
|
149
|
+
<div class="session-files-header">Session Files (${allFiles.length})</div>
|
|
150
|
+
<div class="session-files-list">
|
|
151
|
+
${allFiles.map(
|
|
152
|
+
(attachment) => html`
|
|
153
|
+
<forge-ai-attachment
|
|
154
|
+
.filename=${attachment.filename}
|
|
155
|
+
.size=${attachment.size}
|
|
156
|
+
?uploading=${attachment.uploading ?? false}
|
|
157
|
+
removable
|
|
158
|
+
@forge-ai-attachment-remove=${this._handleAttachmentRemove}>
|
|
159
|
+
</forge-ai-attachment>
|
|
160
|
+
`
|
|
161
|
+
)}
|
|
162
|
+
</div>
|
|
163
|
+
`;
|
|
164
|
+
}
|
|
165
|
+
async _handleSend(evt) {
|
|
166
|
+
const pendingAttachments = [...this._coreController.pendingAttachments];
|
|
167
|
+
await this._coreController.sendMessage({
|
|
168
|
+
content: evt.detail.value,
|
|
169
|
+
timestamp: evt.detail.date.getTime(),
|
|
170
|
+
attachments: pendingAttachments
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
_handleStop() {
|
|
174
|
+
this._coreController.abort();
|
|
175
|
+
}
|
|
176
|
+
_handleCancel() {
|
|
177
|
+
this._handleStop();
|
|
178
|
+
}
|
|
179
|
+
async _handleCopy(evt) {
|
|
180
|
+
const responseId = evt.detail.messageId;
|
|
181
|
+
const responseItem = this._messageItems.find((item) => item.type === "assistant" && item.data.id === responseId);
|
|
182
|
+
if (!responseItem || responseItem.type !== "assistant") {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const textContent = responseItem.data.children.filter(
|
|
186
|
+
(c) => c.type === "text"
|
|
187
|
+
).map((c) => c.content).join("\n\n");
|
|
188
|
+
try {
|
|
189
|
+
await navigator.clipboard.writeText(textContent);
|
|
190
|
+
} catch {
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async _handleUserCopy(evt) {
|
|
194
|
+
const messageId = evt.detail.messageId;
|
|
195
|
+
const message = this._coreController.getMessage(messageId);
|
|
196
|
+
if (!message || message.role !== "user") {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
await navigator.clipboard.writeText(message.content);
|
|
201
|
+
} catch {
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
_handleUserResend(evt) {
|
|
205
|
+
const adapter = this._coreController.adapter;
|
|
206
|
+
if (!adapter) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const messageId = evt.detail.messageId;
|
|
210
|
+
const messageIndex = this._messageItems.findIndex((item) => item.type === "message" && item.data.id === messageId);
|
|
211
|
+
if (messageIndex === -1) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const responseIndex = messageIndex + 1;
|
|
215
|
+
if (responseIndex < this._messageItems.length) {
|
|
216
|
+
this._coreController.removeMessageItemsFrom(responseIndex);
|
|
217
|
+
}
|
|
218
|
+
adapter.sendMessage(this.getMessages());
|
|
219
|
+
}
|
|
220
|
+
_handleUserEdit(evt) {
|
|
221
|
+
const adapter = this._coreController.adapter;
|
|
222
|
+
if (!adapter) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const { messageId, content } = evt.detail;
|
|
226
|
+
const messageIndex = this._messageItems.findIndex((item) => item.type === "message" && item.data.id === messageId);
|
|
227
|
+
if (messageIndex === -1) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
this._coreController.updateMessageContent(messageId, content);
|
|
231
|
+
const responseIndex = messageIndex + 1;
|
|
232
|
+
if (responseIndex < this._messageItems.length) {
|
|
233
|
+
this._coreController.removeMessageItemsFrom(responseIndex);
|
|
234
|
+
}
|
|
235
|
+
adapter.sendMessage(this.getMessages());
|
|
236
|
+
}
|
|
237
|
+
_handleResend(evt) {
|
|
238
|
+
const adapter = this._coreController.adapter;
|
|
239
|
+
if (!adapter) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const responseId = evt.detail.messageId;
|
|
243
|
+
const responseIndex = this._messageItems.findIndex(
|
|
244
|
+
(item) => item.type === "assistant" && item.data.id === responseId
|
|
245
|
+
);
|
|
246
|
+
if (responseIndex === -1) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
this._coreController.removeMessageItemsFrom(responseIndex);
|
|
250
|
+
adapter.sendMessage(this.getMessages());
|
|
251
|
+
}
|
|
252
|
+
_handleFeedback(evt, type) {
|
|
253
|
+
this._coreController.setResponseFeedback(evt.detail.messageId, {
|
|
254
|
+
type,
|
|
255
|
+
reason: evt.detail.feedback
|
|
256
|
+
});
|
|
257
|
+
this._dispatchHostEvent({
|
|
258
|
+
type: "forge-ai-chatbot-response-feedback",
|
|
259
|
+
detail: {
|
|
260
|
+
messageId: evt.detail.messageId,
|
|
261
|
+
type,
|
|
262
|
+
feedback: evt.detail.feedback
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
_handleThumbsUp(evt) {
|
|
267
|
+
this._handleFeedback(evt, "positive");
|
|
268
|
+
}
|
|
269
|
+
_handleThumbsDown(evt) {
|
|
270
|
+
this._handleFeedback(evt, "negative");
|
|
271
|
+
}
|
|
272
|
+
_handleFileSelect(evt) {
|
|
273
|
+
const { file, timestamp } = evt.detail;
|
|
274
|
+
const fileId = this._coreController.processFileUpload(file, timestamp);
|
|
275
|
+
const callbacks = this._coreController.createFileUploadCallbacks(fileId);
|
|
276
|
+
this._dispatchHostEvent({
|
|
277
|
+
type: "forge-ai-chatbot-file-select",
|
|
278
|
+
detail: {
|
|
279
|
+
fileId,
|
|
280
|
+
file,
|
|
281
|
+
filename: file.name,
|
|
282
|
+
size: file.size,
|
|
283
|
+
mimeType: file.type,
|
|
284
|
+
timestamp,
|
|
285
|
+
...callbacks
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
_handleFileError(evt) {
|
|
290
|
+
const errorMessage = {
|
|
291
|
+
id: generateId(),
|
|
292
|
+
role: "assistant",
|
|
293
|
+
content: evt.detail.message,
|
|
294
|
+
timestamp: Date.now(),
|
|
295
|
+
status: "error"
|
|
296
|
+
};
|
|
297
|
+
this._coreController.addMessage(errorMessage);
|
|
298
|
+
}
|
|
299
|
+
async _handleSuggestionSelect(evt) {
|
|
300
|
+
this._promptRef.value?.addToHistory(evt.detail.text);
|
|
301
|
+
await this.sendMessage(evt.detail.text);
|
|
302
|
+
this._promptRef.value?.focus();
|
|
303
|
+
}
|
|
304
|
+
_handleVoiceInputResult(evt) {
|
|
305
|
+
const { transcript } = evt.detail;
|
|
306
|
+
if (transcript && this._promptRef.value) {
|
|
307
|
+
this._promptRef.value.value = transcript;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
clearMessages() {
|
|
311
|
+
const event = this._dispatchHostEvent({ type: "forge-ai-chatbot-clear", cancelable: true });
|
|
312
|
+
if (event.defaultPrevented) {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
this._coreController.clearMessages();
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
getMessages() {
|
|
319
|
+
return this._coreController.getMessages();
|
|
320
|
+
}
|
|
321
|
+
setMessages(messages) {
|
|
322
|
+
this._coreController.setMessages(messages);
|
|
323
|
+
}
|
|
324
|
+
async sendMessage(content, files) {
|
|
325
|
+
if (!this._coreController.adapter) {
|
|
326
|
+
console.warn("No adapter configured.");
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (files) {
|
|
330
|
+
const timestamp = Date.now();
|
|
331
|
+
for (const file of files) {
|
|
332
|
+
const fileId = this._coreController.processFileUpload(file, timestamp);
|
|
333
|
+
const callbacks = this._coreController.createFileUploadCallbacks(fileId);
|
|
334
|
+
this._dispatchHostEvent({
|
|
335
|
+
type: "forge-ai-chatbot-file-select",
|
|
336
|
+
detail: {
|
|
337
|
+
fileId,
|
|
338
|
+
file,
|
|
339
|
+
filename: file.name,
|
|
340
|
+
size: file.size,
|
|
341
|
+
mimeType: file.type,
|
|
342
|
+
timestamp,
|
|
343
|
+
...callbacks
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
await this._coreController.sendMessage({
|
|
349
|
+
content,
|
|
350
|
+
attachments: this._coreController.pendingAttachments
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
abort() {
|
|
354
|
+
this._handleStop();
|
|
355
|
+
}
|
|
356
|
+
async scrollToBottom({ behavior } = {}) {
|
|
357
|
+
await this._messageThreadRef.value?.updateComplete;
|
|
358
|
+
await new Promise(requestAnimationFrame);
|
|
359
|
+
this._messageThreadRef.value?.scrollToBottom({ behavior });
|
|
360
|
+
}
|
|
361
|
+
getThreadState() {
|
|
362
|
+
return {
|
|
363
|
+
threadId: this._coreController.adapter?.threadId,
|
|
364
|
+
messages: this.getMessages(),
|
|
365
|
+
timestamp: Date.now(),
|
|
366
|
+
selectedAgentId: this.selectedAgentId
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
getSelectedAgent() {
|
|
370
|
+
return this.agents.find((a) => a.id === this.selectedAgentId);
|
|
371
|
+
}
|
|
372
|
+
async setThreadState(threadState) {
|
|
373
|
+
this.setMessages(threadState.messages);
|
|
374
|
+
this.selectedAgentId = threadState.selectedAgentId;
|
|
375
|
+
if (threadState.threadId && this._coreController.adapter) {
|
|
376
|
+
this._coreController.adapter.threadId = threadState.threadId;
|
|
377
|
+
}
|
|
378
|
+
await this.updateComplete;
|
|
379
|
+
const userMessages = threadState.messages.filter((msg) => msg.role === "user").map((msg) => msg.content);
|
|
380
|
+
this._promptRef.value?.setHistory(userMessages);
|
|
381
|
+
this.scrollToBottom({ behavior: "instant" });
|
|
382
|
+
}
|
|
383
|
+
_dispatchHostEvent(config) {
|
|
384
|
+
const event = new CustomEvent(config.type, {
|
|
385
|
+
detail: config.detail,
|
|
386
|
+
bubbles: true,
|
|
387
|
+
composed: true,
|
|
388
|
+
cancelable: config.cancelable ?? false
|
|
389
|
+
});
|
|
390
|
+
this.dispatchEvent(event);
|
|
391
|
+
return event;
|
|
392
|
+
}
|
|
393
|
+
_formatToolCallForExport(toolCall) {
|
|
394
|
+
const lines = [` Tool: ${toolCall.name}`, ` Args: ${JSON.stringify(toolCall.args)}`];
|
|
395
|
+
if (toolCall.status === "error") {
|
|
396
|
+
const errorMsg = typeof toolCall.result === "object" && toolCall.result !== null && "error" in toolCall.result ? toolCall.result.error : "Unknown error";
|
|
397
|
+
lines.push(` Error: ${errorMsg}`);
|
|
398
|
+
} else if (toolCall.result !== void 0) {
|
|
399
|
+
lines.push(` Result: ${JSON.stringify(toolCall.result)}`);
|
|
400
|
+
}
|
|
401
|
+
return lines.join("\n");
|
|
402
|
+
}
|
|
403
|
+
_handleExport() {
|
|
404
|
+
const messages = this.getMessages();
|
|
405
|
+
if (messages.length === 0) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const chatText = messages.map((message) => {
|
|
409
|
+
const timestamp = new Date(message.timestamp).toLocaleString();
|
|
410
|
+
const role = message.role === "user" ? "You" : "Assistant";
|
|
411
|
+
let output = `[${timestamp}] ${role}:
|
|
412
|
+
${message.content}
|
|
413
|
+
`;
|
|
414
|
+
if (message.toolCalls?.length) {
|
|
415
|
+
output += "\n" + message.toolCalls.map((tc) => this._formatToolCallForExport(tc)).join("\n\n") + "\n";
|
|
416
|
+
}
|
|
417
|
+
return output;
|
|
418
|
+
}).join("\n");
|
|
419
|
+
const filename = `chat-history-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-")}.txt`;
|
|
420
|
+
downloadFile(chatText, filename, "text/plain");
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
__decorateClass([
|
|
424
|
+
property({ attribute: false })
|
|
425
|
+
], AiChatbotBase.prototype, "adapter");
|
|
426
|
+
__decorateClass([
|
|
427
|
+
property({ attribute: "file-upload" })
|
|
428
|
+
], AiChatbotBase.prototype, "fileUpload");
|
|
429
|
+
__decorateClass([
|
|
430
|
+
property({ attribute: "voice-input" })
|
|
431
|
+
], AiChatbotBase.prototype, "voiceInput");
|
|
432
|
+
__decorateClass([
|
|
433
|
+
property()
|
|
434
|
+
], AiChatbotBase.prototype, "placeholder");
|
|
435
|
+
__decorateClass([
|
|
436
|
+
property({ attribute: false })
|
|
437
|
+
], AiChatbotBase.prototype, "suggestions");
|
|
438
|
+
__decorateClass([
|
|
439
|
+
property({ type: Boolean, attribute: "enable-reactions" })
|
|
440
|
+
], AiChatbotBase.prototype, "enableReactions");
|
|
441
|
+
__decorateClass([
|
|
442
|
+
property({ attribute: "title-text" })
|
|
443
|
+
], AiChatbotBase.prototype, "titleText");
|
|
444
|
+
__decorateClass([
|
|
445
|
+
property({ attribute: "heading-level", type: Number })
|
|
446
|
+
], AiChatbotBase.prototype, "headingLevel");
|
|
447
|
+
__decorateClass([
|
|
448
|
+
property({ type: Boolean, attribute: "debug-mode" })
|
|
449
|
+
], AiChatbotBase.prototype, "debugMode");
|
|
450
|
+
__decorateClass([
|
|
451
|
+
property({ attribute: "disclaimer-text" })
|
|
452
|
+
], AiChatbotBase.prototype, "disclaimerText");
|
|
453
|
+
__decorateClass([
|
|
454
|
+
property({ attribute: "debug-command" })
|
|
455
|
+
], AiChatbotBase.prototype, "debugCommand");
|
|
456
|
+
__decorateClass([
|
|
457
|
+
property({ type: Object, attribute: false })
|
|
458
|
+
], AiChatbotBase.prototype, "agentInfo");
|
|
459
|
+
__decorateClass([
|
|
460
|
+
property({ attribute: false })
|
|
461
|
+
], AiChatbotBase.prototype, "agents");
|
|
462
|
+
__decorateClass([
|
|
463
|
+
property({ attribute: "selected-agent-id" })
|
|
464
|
+
], AiChatbotBase.prototype, "selectedAgentId");
|
|
465
|
+
export {
|
|
466
|
+
AiChatbotBase
|
|
467
|
+
};
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { Agent, ChatMessage, ForgeAiChatbotFileSelectEventData, HeadingLevel, ThreadState } from './types.js';
|
|
1
|
+
import { TemplateResult } from 'lit';
|
|
2
|
+
import { AiMessageThreadComponent } from '../ai-message-thread';
|
|
3
|
+
import { AiPromptComponent } from '../ai-prompt';
|
|
4
|
+
import { AiChatbotBase } from './ai-chatbot-base.js';
|
|
5
|
+
import { Agent, ChatMessage } from './types.js';
|
|
7
6
|
declare global {
|
|
8
7
|
interface HTMLElementTagNameMap {
|
|
9
8
|
'forge-ai-chatbot': AiChatbotComponent;
|
|
@@ -20,7 +19,6 @@ declare global {
|
|
|
20
19
|
'forge-ai-chatbot-clear': CustomEvent<void>;
|
|
21
20
|
'forge-ai-chatbot-info': CustomEvent<void>;
|
|
22
21
|
'forge-ai-chatbot-file-select': CustomEvent<ForgeAiChatbotFileSelectEventData>;
|
|
23
|
-
'forge-ai-voice-input-result': CustomEvent<ForgeAiVoiceInputResultEvent>;
|
|
24
22
|
'forge-ai-chatbot-file-remove': CustomEvent<ForgeAiChatbotFileRemoveEventData>;
|
|
25
23
|
'forge-ai-chatbot-response-feedback': CustomEvent<ForgeAiChatbotResponseFeedbackEventData>;
|
|
26
24
|
'forge-ai-chatbot-agent-change': CustomEvent<ForgeAiChatbotAgentChangeEventData>;
|
|
@@ -38,6 +36,20 @@ export interface ForgeAiChatbotToolCallEventData {
|
|
|
38
36
|
export interface ForgeAiChatbotErrorEventData {
|
|
39
37
|
error: string;
|
|
40
38
|
}
|
|
39
|
+
export interface ForgeAiChatbotFileSelectEventData {
|
|
40
|
+
fileId: string;
|
|
41
|
+
file: File;
|
|
42
|
+
filename: string;
|
|
43
|
+
size: number;
|
|
44
|
+
mimeType: string;
|
|
45
|
+
timestamp: number;
|
|
46
|
+
onProgress: (progress: number) => void;
|
|
47
|
+
onComplete: (result: {
|
|
48
|
+
url?: string;
|
|
49
|
+
error?: string;
|
|
50
|
+
}) => void;
|
|
51
|
+
onAbort: () => void;
|
|
52
|
+
}
|
|
41
53
|
export interface ForgeAiChatbotFileRemoveEventData {
|
|
42
54
|
fileId: string;
|
|
43
55
|
}
|
|
@@ -51,19 +63,15 @@ export interface ForgeAiChatbotAgentChangeEventData {
|
|
|
51
63
|
previousAgentId: string | undefined;
|
|
52
64
|
}
|
|
53
65
|
export declare const AiChatbotComponentTagName: keyof HTMLElementTagNameMap;
|
|
54
|
-
/**
|
|
55
|
-
* Type for feature toggle values
|
|
56
|
-
*/
|
|
57
|
-
export type FeatureToggle = 'on' | 'off';
|
|
58
66
|
/**
|
|
59
67
|
* @tag forge-ai-chatbot
|
|
60
68
|
*
|
|
61
|
-
* @summary A complete, self-contained AI chatbot component
|
|
69
|
+
* @summary A complete, self-contained AI chatbot component.
|
|
62
70
|
*
|
|
63
71
|
* @description
|
|
64
72
|
* The AI Chatbot component provides a full-featured chat interface with support for streaming responses,
|
|
65
73
|
* client-side tool execution, file attachments, markdown rendering, and programmatic control.
|
|
66
|
-
* It uses an adapter pattern to abstract communication, allowing for
|
|
74
|
+
* It uses an adapter pattern to abstract communication, allowing for any protocol implementation.
|
|
67
75
|
*
|
|
68
76
|
* @slot header - Slot for custom header content
|
|
69
77
|
* @slot icon - Slot for custom header icon (default: forge-ai-icon)
|
|
@@ -89,74 +97,17 @@ export type FeatureToggle = 'on' | 'off';
|
|
|
89
97
|
* @event {CustomEvent<void>} forge-ai-chatbot-info - Fired when header info option is selected
|
|
90
98
|
* @event {CustomEvent<ForgeAiChatbotResponseFeedbackEventData>} forge-ai-chatbot-response-feedback - Fired when user provides feedback on a response (thumbs up/down)
|
|
91
99
|
*/
|
|
92
|
-
export declare class AiChatbotComponent extends
|
|
100
|
+
export declare class AiChatbotComponent extends AiChatbotBase {
|
|
93
101
|
#private;
|
|
94
102
|
static styles: import('lit').CSSResult;
|
|
95
|
-
adapter?: AgentAdapter;
|
|
96
|
-
fileUpload: FeatureToggle;
|
|
97
|
-
voiceInput: FeatureToggle;
|
|
98
|
-
debugCommand: FeatureToggle;
|
|
99
|
-
placeholder: string;
|
|
100
|
-
suggestions?: Suggestion[];
|
|
101
103
|
showExpandButton: boolean;
|
|
102
104
|
showMinimizeButton: boolean;
|
|
103
105
|
expanded: boolean;
|
|
104
106
|
minimizeIcon: 'default' | 'panel';
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
headingLevel: HeadingLevel;
|
|
109
|
-
debugMode: boolean;
|
|
110
|
-
disclaimerText: string | null | undefined;
|
|
111
|
-
agents: Agent[];
|
|
112
|
-
selectedAgentId?: string;
|
|
113
|
-
connectedCallback(): void;
|
|
114
|
-
disconnectedCallback(): void;
|
|
115
|
-
willUpdate(changedProperties: PropertyValues<this>): void;
|
|
116
|
-
/**
|
|
117
|
-
* Clears all messages from the chat history.
|
|
118
|
-
*/
|
|
119
|
-
clearMessages(): void;
|
|
120
|
-
/**
|
|
121
|
-
* Gets the current message history.
|
|
122
|
-
* @returns Array of chat messages
|
|
123
|
-
*/
|
|
124
|
-
getMessages(): ChatMessage[];
|
|
125
|
-
/**
|
|
126
|
-
* Sets the message history. Useful for restoring conversation state.
|
|
127
|
-
* @param messages - Array of chat messages to set
|
|
128
|
-
*/
|
|
129
|
-
setMessages(messages: ChatMessage[]): void;
|
|
130
|
-
/**
|
|
131
|
-
* Gets the currently selected agent.
|
|
132
|
-
* @returns The selected agent or undefined if none selected
|
|
133
|
-
*/
|
|
134
|
-
getSelectedAgent(): Agent | undefined;
|
|
135
|
-
/**
|
|
136
|
-
* Programmatically sends a message as the user.
|
|
137
|
-
* @param content - The message content to send
|
|
138
|
-
* @param files - Optional file objects to attach
|
|
139
|
-
*/
|
|
107
|
+
protected _messageThreadRef: import('lit/directives/ref').Ref<AiMessageThreadComponent>;
|
|
108
|
+
protected _promptRef: import('lit/directives/ref').Ref<AiPromptComponent>;
|
|
109
|
+
protected _handleInfo(): void;
|
|
140
110
|
sendMessage(content: string, files?: File[]): Promise<void>;
|
|
141
|
-
/**
|
|
142
|
-
* Aborts the current streaming response.
|
|
143
|
-
*/
|
|
144
|
-
abort(): void;
|
|
145
|
-
/**
|
|
146
|
-
* Scrolls the chat interface to the bottom.
|
|
147
|
-
*/
|
|
148
|
-
scrollToBottom({ behavior }?: {
|
|
149
|
-
behavior?: ScrollBehavior;
|
|
150
|
-
}): Promise<void>;
|
|
151
|
-
/**
|
|
152
|
-
* Gets the complete serializable thread state including threadId and messages.
|
|
153
|
-
* @returns ThreadState object containing threadId, messages, and timestamp
|
|
154
|
-
*/
|
|
155
|
-
getThreadState(): ThreadState;
|
|
156
|
-
/**
|
|
157
|
-
* Restores thread state from a serialized ThreadState object.
|
|
158
|
-
* @param state - ThreadState object to restore
|
|
159
|
-
*/
|
|
160
|
-
setThreadState(state: ThreadState): Promise<void>;
|
|
161
111
|
render(): TemplateResult;
|
|
162
112
|
}
|
|
113
|
+
export type { FeatureToggle } from './ai-chatbot-base.js';
|