@jupyterlite/ai 0.8.1 → 0.9.0-a1
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/lib/agent.d.ts +243 -0
- package/lib/agent.js +627 -0
- package/lib/chat-model.d.ts +195 -0
- package/lib/chat-model.js +591 -0
- package/lib/completion/completion-provider.d.ts +93 -0
- package/lib/completion/completion-provider.js +235 -0
- package/lib/completion/index.d.ts +1 -0
- package/lib/completion/index.js +1 -0
- package/lib/components/clear-button.d.ts +18 -0
- package/lib/components/clear-button.js +31 -0
- package/lib/components/index.d.ts +3 -0
- package/lib/components/index.js +3 -0
- package/lib/components/model-select.d.ts +19 -0
- package/lib/components/model-select.js +154 -0
- package/lib/components/stop-button.d.ts +3 -3
- package/lib/components/stop-button.js +8 -9
- package/lib/components/token-usage-display.d.ts +45 -0
- package/lib/components/token-usage-display.js +74 -0
- package/lib/components/tool-select.d.ts +27 -0
- package/lib/components/tool-select.js +130 -0
- package/lib/icons.d.ts +3 -1
- package/lib/icons.js +10 -13
- package/lib/index.d.ts +5 -5
- package/lib/index.js +341 -169
- package/lib/mcp/browser.d.ts +68 -0
- package/lib/mcp/browser.js +132 -0
- package/lib/models/settings-model.d.ts +70 -0
- package/lib/models/settings-model.js +296 -0
- package/lib/providers/built-in-providers.d.ts +9 -0
- package/lib/providers/built-in-providers.js +266 -0
- package/lib/providers/models.d.ts +37 -0
- package/lib/providers/models.js +28 -0
- package/lib/providers/provider-registry.d.ts +94 -0
- package/lib/providers/provider-registry.js +155 -0
- package/lib/tokens.d.ts +167 -86
- package/lib/tokens.js +25 -12
- package/lib/tools/commands.d.ts +11 -0
- package/lib/tools/commands.js +126 -0
- package/lib/tools/file.d.ts +27 -0
- package/lib/tools/file.js +262 -0
- package/lib/tools/notebook.d.ts +41 -0
- package/lib/tools/notebook.js +779 -0
- package/lib/tools/tool-registry.d.ts +35 -0
- package/lib/tools/tool-registry.js +55 -0
- package/lib/widgets/ai-settings.d.ts +49 -0
- package/lib/widgets/ai-settings.js +580 -0
- package/lib/widgets/chat-wrapper.d.ts +144 -0
- package/lib/widgets/chat-wrapper.js +390 -0
- package/lib/widgets/provider-config-dialog.d.ts +14 -0
- package/lib/widgets/provider-config-dialog.js +112 -0
- package/package.json +151 -40
- package/schema/settings-model.json +159 -0
- package/src/agent.ts +836 -0
- package/src/chat-model.ts +771 -0
- package/src/completion/completion-provider.ts +346 -0
- package/src/completion/index.ts +1 -0
- package/src/components/clear-button.tsx +56 -0
- package/src/components/index.ts +3 -0
- package/src/components/model-select.tsx +245 -0
- package/src/components/stop-button.tsx +11 -11
- package/src/components/token-usage-display.tsx +130 -0
- package/src/components/tool-select.tsx +218 -0
- package/src/icons.ts +12 -14
- package/src/index.ts +485 -232
- package/src/mcp/browser.ts +213 -0
- package/src/models/settings-model.ts +413 -0
- package/src/providers/built-in-providers.ts +294 -0
- package/src/providers/models.ts +79 -0
- package/src/providers/provider-registry.ts +189 -0
- package/src/tokens.ts +217 -90
- package/src/tools/commands.ts +151 -0
- package/src/tools/file.ts +307 -0
- package/src/tools/notebook.ts +987 -0
- package/src/tools/tool-registry.ts +63 -0
- package/src/types.d.ts +4 -0
- package/src/widgets/ai-settings.tsx +1233 -0
- package/src/widgets/chat-wrapper.tsx +543 -0
- package/src/widgets/provider-config-dialog.tsx +272 -0
- package/style/base.css +335 -14
- package/style/icons/jupyternaut-lite.svg +1 -1
- package/lib/base-completer.d.ts +0 -49
- package/lib/base-completer.js +0 -14
- package/lib/chat-handler.d.ts +0 -56
- package/lib/chat-handler.js +0 -201
- package/lib/completion-provider.d.ts +0 -34
- package/lib/completion-provider.js +0 -32
- package/lib/default-prompts.d.ts +0 -2
- package/lib/default-prompts.js +0 -31
- package/lib/default-providers/Anthropic/completer.d.ts +0 -12
- package/lib/default-providers/Anthropic/completer.js +0 -46
- package/lib/default-providers/Anthropic/settings-schema.json +0 -70
- package/lib/default-providers/ChromeAI/completer.d.ts +0 -12
- package/lib/default-providers/ChromeAI/completer.js +0 -56
- package/lib/default-providers/ChromeAI/instructions.d.ts +0 -6
- package/lib/default-providers/ChromeAI/instructions.js +0 -42
- package/lib/default-providers/ChromeAI/settings-schema.json +0 -18
- package/lib/default-providers/Gemini/completer.d.ts +0 -12
- package/lib/default-providers/Gemini/completer.js +0 -48
- package/lib/default-providers/Gemini/instructions.d.ts +0 -2
- package/lib/default-providers/Gemini/instructions.js +0 -9
- package/lib/default-providers/Gemini/settings-schema.json +0 -64
- package/lib/default-providers/MistralAI/completer.d.ts +0 -13
- package/lib/default-providers/MistralAI/completer.js +0 -52
- package/lib/default-providers/MistralAI/instructions.d.ts +0 -2
- package/lib/default-providers/MistralAI/instructions.js +0 -18
- package/lib/default-providers/MistralAI/settings-schema.json +0 -75
- package/lib/default-providers/Ollama/completer.d.ts +0 -12
- package/lib/default-providers/Ollama/completer.js +0 -43
- package/lib/default-providers/Ollama/instructions.d.ts +0 -2
- package/lib/default-providers/Ollama/instructions.js +0 -70
- package/lib/default-providers/Ollama/settings-schema.json +0 -143
- package/lib/default-providers/OpenAI/completer.d.ts +0 -12
- package/lib/default-providers/OpenAI/completer.js +0 -43
- package/lib/default-providers/OpenAI/settings-schema.json +0 -628
- package/lib/default-providers/WebLLM/completer.d.ts +0 -21
- package/lib/default-providers/WebLLM/completer.js +0 -127
- package/lib/default-providers/WebLLM/instructions.d.ts +0 -6
- package/lib/default-providers/WebLLM/instructions.js +0 -32
- package/lib/default-providers/WebLLM/settings-schema.json +0 -19
- package/lib/default-providers/index.d.ts +0 -2
- package/lib/default-providers/index.js +0 -179
- package/lib/provider.d.ts +0 -144
- package/lib/provider.js +0 -412
- package/lib/settings/base.json +0 -7
- package/lib/settings/index.d.ts +0 -3
- package/lib/settings/index.js +0 -3
- package/lib/settings/panel.d.ts +0 -226
- package/lib/settings/panel.js +0 -510
- package/lib/settings/textarea.d.ts +0 -2
- package/lib/settings/textarea.js +0 -18
- package/lib/settings/utils.d.ts +0 -2
- package/lib/settings/utils.js +0 -4
- package/lib/types/ai-model.d.ts +0 -24
- package/lib/types/ai-model.js +0 -5
- package/schema/chat.json +0 -28
- package/schema/provider-registry.json +0 -29
- package/schema/system-prompts.json +0 -22
- package/src/base-completer.ts +0 -75
- package/src/chat-handler.ts +0 -262
- package/src/completion-provider.ts +0 -64
- package/src/default-prompts.ts +0 -33
- package/src/default-providers/Anthropic/completer.ts +0 -59
- package/src/default-providers/ChromeAI/completer.ts +0 -73
- package/src/default-providers/ChromeAI/instructions.ts +0 -45
- package/src/default-providers/Gemini/completer.ts +0 -61
- package/src/default-providers/Gemini/instructions.ts +0 -9
- package/src/default-providers/MistralAI/completer.ts +0 -69
- package/src/default-providers/MistralAI/instructions.ts +0 -18
- package/src/default-providers/Ollama/completer.ts +0 -54
- package/src/default-providers/Ollama/instructions.ts +0 -70
- package/src/default-providers/OpenAI/completer.ts +0 -54
- package/src/default-providers/WebLLM/completer.ts +0 -151
- package/src/default-providers/WebLLM/instructions.ts +0 -33
- package/src/default-providers/index.ts +0 -211
- package/src/global.d.ts +0 -9
- package/src/provider.ts +0 -514
- package/src/settings/index.ts +0 -3
- package/src/settings/panel.tsx +0 -773
- package/src/settings/textarea.tsx +0 -33
- package/src/settings/utils.ts +0 -5
- package/src/types/ai-model.ts +0 -37
- package/src/types/service-worker.d.ts +0 -6
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
import {
|
|
2
|
+
settingsIcon,
|
|
3
|
+
Toolbar,
|
|
4
|
+
ToolbarButton
|
|
5
|
+
} from '@jupyterlab/ui-components';
|
|
6
|
+
import { CommandRegistry } from '@lumino/commands';
|
|
7
|
+
import { Panel, Widget } from '@lumino/widgets';
|
|
8
|
+
import { AIChatModel } from '../chat-model';
|
|
9
|
+
import { TokenUsageWidget } from '../components/token-usage-display';
|
|
10
|
+
import { AISettingsModel } from '../models/settings-model';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* CSS class for the chat toolbar
|
|
14
|
+
*/
|
|
15
|
+
const CHAT_TOOLBAR_CLASS = 'jp-AIChatToolbar';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* CSS class for the chat panel
|
|
19
|
+
*/
|
|
20
|
+
const CHAT_PANEL_CLASS = 'jp-AIChatPanel';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A widget wrapper for the chat panel that provides toolbar functionality
|
|
24
|
+
* and approval button handling for AI tool calls.
|
|
25
|
+
*/
|
|
26
|
+
export class ChatWrapperWidget extends Panel {
|
|
27
|
+
/**
|
|
28
|
+
* Constructs a new ChatWrapperWidget.
|
|
29
|
+
*
|
|
30
|
+
* @param options - Configuration options for the widget
|
|
31
|
+
*/
|
|
32
|
+
constructor(options: ChatWrapperWidget.IOptions) {
|
|
33
|
+
super();
|
|
34
|
+
this._chatPanel = options.chatPanel;
|
|
35
|
+
this._chatModel = options.chatModel;
|
|
36
|
+
this._settingsModel = options.settingsModel;
|
|
37
|
+
this._commands = options.commands;
|
|
38
|
+
this._toolbar = this._createToolbar();
|
|
39
|
+
|
|
40
|
+
this.id = '@jupyterlite/ai:chat-wrapper';
|
|
41
|
+
this.title.caption = 'Chat with AI assistant';
|
|
42
|
+
this.title.icon = this._chatPanel.title.icon;
|
|
43
|
+
|
|
44
|
+
this.addClass('jp-AIChatWrapper');
|
|
45
|
+
|
|
46
|
+
this._toolbar.addClass(CHAT_TOOLBAR_CLASS);
|
|
47
|
+
this._chatPanel.addClass(CHAT_PANEL_CLASS);
|
|
48
|
+
|
|
49
|
+
// Add widgets to the panel
|
|
50
|
+
this.addWidget(this._toolbar);
|
|
51
|
+
this.addWidget(this._chatPanel);
|
|
52
|
+
|
|
53
|
+
// Set up approval button event handling
|
|
54
|
+
this._setupApprovalHandlers();
|
|
55
|
+
|
|
56
|
+
// Set up message processing for approval buttons
|
|
57
|
+
this._setupMessageProcessing();
|
|
58
|
+
|
|
59
|
+
// Fix the focus issue: override the global click handler
|
|
60
|
+
// TODO: remove after https://github.com/jupyterlab/jupyter-chat/issues/267
|
|
61
|
+
this._fixCopyFocusIssue();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Creates and configures the toolbar with token usage display and settings button.
|
|
66
|
+
*
|
|
67
|
+
* TODO: integrate with IToolbarRegistry to allow adding custom toolbar items
|
|
68
|
+
*
|
|
69
|
+
* @returns The configured toolbar widget
|
|
70
|
+
*/
|
|
71
|
+
private _createToolbar() {
|
|
72
|
+
const toolbar = new Toolbar();
|
|
73
|
+
const tokenUsageWidget = new TokenUsageWidget({
|
|
74
|
+
tokenUsageChanged: this._chatModel.tokenUsageChanged,
|
|
75
|
+
settingsModel: this._settingsModel
|
|
76
|
+
});
|
|
77
|
+
toolbar.addItem('token-usage', tokenUsageWidget);
|
|
78
|
+
|
|
79
|
+
toolbar.addItem('spacer', Toolbar.createSpacerItem());
|
|
80
|
+
toolbar.addItem(
|
|
81
|
+
'settings',
|
|
82
|
+
new ToolbarButton({
|
|
83
|
+
icon: settingsIcon,
|
|
84
|
+
onClick: () => {
|
|
85
|
+
this._commands.execute('@jupyterlite/ai:open-settings');
|
|
86
|
+
},
|
|
87
|
+
tooltip: 'Open AI Settings'
|
|
88
|
+
})
|
|
89
|
+
);
|
|
90
|
+
return toolbar;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Sets up event handlers for existing approval buttons in the chat panel.
|
|
95
|
+
*/
|
|
96
|
+
private _setupApprovalHandlers() {
|
|
97
|
+
// This method will be called to add handlers to existing buttons
|
|
98
|
+
// New buttons get handlers added in _processApprovalButtons
|
|
99
|
+
const existingButtons = this._chatPanel.node.querySelectorAll(
|
|
100
|
+
'.jp-ai-approval-btn'
|
|
101
|
+
);
|
|
102
|
+
existingButtons.forEach(button => {
|
|
103
|
+
this._addButtonHandler(button as HTMLButtonElement);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Adds click event handler to an approval button.
|
|
109
|
+
*
|
|
110
|
+
* @param button - The button element to add handler to
|
|
111
|
+
*/
|
|
112
|
+
private _addButtonHandler(button: HTMLButtonElement) {
|
|
113
|
+
// Remove any existing listeners to avoid duplicates
|
|
114
|
+
button.removeEventListener('click', this._handleButtonClick);
|
|
115
|
+
button.addEventListener('click', this._handleButtonClick);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Handles click events for individual approval buttons.
|
|
120
|
+
*
|
|
121
|
+
* @param event - The click event
|
|
122
|
+
*/
|
|
123
|
+
private _handleButtonClick = async (event: Event) => {
|
|
124
|
+
const target = event.target as HTMLElement;
|
|
125
|
+
event.preventDefault();
|
|
126
|
+
event.stopPropagation();
|
|
127
|
+
|
|
128
|
+
const buttonsContainer = target.closest('.jp-ai-tool-approval-buttons');
|
|
129
|
+
if (!buttonsContainer) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const interruptionId = buttonsContainer.getAttribute(
|
|
134
|
+
'data-interruption-id'
|
|
135
|
+
);
|
|
136
|
+
if (!interruptionId) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Get message ID for updating the tool call box
|
|
141
|
+
const messageId = buttonsContainer.getAttribute('data-message-id');
|
|
142
|
+
|
|
143
|
+
// Hide buttons immediately and show status
|
|
144
|
+
const isApprove = target.classList.contains('jp-ai-approval-approve');
|
|
145
|
+
this._showApprovalStatus(buttonsContainer, isApprove);
|
|
146
|
+
|
|
147
|
+
if (isApprove) {
|
|
148
|
+
// Execute approval with message ID for updating the tool call box
|
|
149
|
+
await this._chatModel.approveToolCall(
|
|
150
|
+
interruptionId,
|
|
151
|
+
messageId || undefined
|
|
152
|
+
);
|
|
153
|
+
} else if (target.classList.contains('jp-ai-approval-reject')) {
|
|
154
|
+
// Execute rejection with message ID for updating the tool call box
|
|
155
|
+
await this._chatModel.rejectToolCall(
|
|
156
|
+
interruptionId,
|
|
157
|
+
messageId || undefined
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Adds click event handler to a grouped approval button.
|
|
164
|
+
*
|
|
165
|
+
* @param button - The button element to add handler to
|
|
166
|
+
*/
|
|
167
|
+
private _addGroupedButtonHandler(button: HTMLButtonElement) {
|
|
168
|
+
// Remove any existing listeners to avoid duplicates
|
|
169
|
+
button.removeEventListener('click', this._handleGroupedButtonClick);
|
|
170
|
+
button.addEventListener('click', this._handleGroupedButtonClick);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Handles click events for grouped approval buttons.
|
|
175
|
+
*
|
|
176
|
+
* @param event - The click event
|
|
177
|
+
*/
|
|
178
|
+
private _handleGroupedButtonClick = async (event: Event) => {
|
|
179
|
+
const target = event.target as HTMLElement;
|
|
180
|
+
event.preventDefault();
|
|
181
|
+
event.stopPropagation();
|
|
182
|
+
|
|
183
|
+
const buttonsContainer = target.closest('.jp-ai-group-approval-buttons');
|
|
184
|
+
if (!buttonsContainer) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const groupId = buttonsContainer.getAttribute('data-group-id');
|
|
189
|
+
const interruptionIdsStr = buttonsContainer.getAttribute(
|
|
190
|
+
'data-interruption-ids'
|
|
191
|
+
);
|
|
192
|
+
if (!groupId || !interruptionIdsStr) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const interruptionIds = interruptionIdsStr.split(',');
|
|
197
|
+
const messageId = buttonsContainer.getAttribute('data-message-id');
|
|
198
|
+
|
|
199
|
+
// Hide buttons immediately and show status
|
|
200
|
+
const isApprove = target.classList.contains('jp-ai-group-approve-all');
|
|
201
|
+
this._showGroupApprovalStatus(buttonsContainer, isApprove);
|
|
202
|
+
|
|
203
|
+
if (isApprove) {
|
|
204
|
+
// Execute grouped approval
|
|
205
|
+
await this._chatModel.approveGroupedToolCalls(
|
|
206
|
+
groupId,
|
|
207
|
+
interruptionIds,
|
|
208
|
+
messageId || undefined
|
|
209
|
+
);
|
|
210
|
+
} else if (target.classList.contains('jp-ai-group-reject-all')) {
|
|
211
|
+
// Execute grouped rejection
|
|
212
|
+
await this._chatModel.rejectGroupedToolCalls(
|
|
213
|
+
groupId,
|
|
214
|
+
interruptionIds,
|
|
215
|
+
messageId || undefined
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Shows approval status by replacing buttons with status indicator.
|
|
222
|
+
*
|
|
223
|
+
* @param buttonsContainer - The container element holding the buttons
|
|
224
|
+
* @param isApprove - Whether the action was approval or rejection
|
|
225
|
+
*/
|
|
226
|
+
private _showApprovalStatus(
|
|
227
|
+
buttonsContainer: Element,
|
|
228
|
+
isApprove: boolean
|
|
229
|
+
): void {
|
|
230
|
+
// Clear the container and add status indicator
|
|
231
|
+
buttonsContainer.innerHTML = '';
|
|
232
|
+
|
|
233
|
+
const statusDiv = document.createElement('div');
|
|
234
|
+
statusDiv.className = `jp-ai-approval-status ${isApprove ? 'jp-ai-approval-status-approved' : 'jp-ai-approval-status-rejected'}`;
|
|
235
|
+
|
|
236
|
+
const icon = document.createElement('span');
|
|
237
|
+
icon.className = 'jp-ai-approval-icon';
|
|
238
|
+
icon.textContent = isApprove ? '✅' : '❌';
|
|
239
|
+
|
|
240
|
+
const text = document.createElement('span');
|
|
241
|
+
text.textContent = isApprove ? 'Tools approved' : 'Tools rejected';
|
|
242
|
+
|
|
243
|
+
statusDiv.appendChild(icon);
|
|
244
|
+
statusDiv.appendChild(text);
|
|
245
|
+
buttonsContainer.appendChild(statusDiv);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Shows group approval status by replacing buttons with status indicator.
|
|
250
|
+
*
|
|
251
|
+
* @param buttonsContainer - The container element holding the buttons
|
|
252
|
+
* @param isApprove - Whether the action was approval or rejection
|
|
253
|
+
* @param toolCount - The number of tools that were approved/rejected
|
|
254
|
+
*/
|
|
255
|
+
private _showGroupApprovalStatus(
|
|
256
|
+
buttonsContainer: Element,
|
|
257
|
+
isApprove: boolean
|
|
258
|
+
): void {
|
|
259
|
+
// Clear the container and add status indicator
|
|
260
|
+
buttonsContainer.innerHTML = '';
|
|
261
|
+
|
|
262
|
+
const statusDiv = document.createElement('div');
|
|
263
|
+
statusDiv.className = `jp-ai-group-approval-status ${isApprove ? 'jp-ai-group-approval-status-approved' : 'jp-ai-group-approval-status-rejected'}`;
|
|
264
|
+
|
|
265
|
+
const icon = document.createElement('span');
|
|
266
|
+
icon.className = 'jp-ai-approval-icon';
|
|
267
|
+
icon.textContent = isApprove ? '✅' : '❌';
|
|
268
|
+
|
|
269
|
+
const text = document.createElement('span');
|
|
270
|
+
text.textContent = isApprove ? 'Tools approved' : 'Tools rejected';
|
|
271
|
+
|
|
272
|
+
statusDiv.appendChild(icon);
|
|
273
|
+
statusDiv.appendChild(text);
|
|
274
|
+
buttonsContainer.appendChild(statusDiv);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Sets up mutation observer to watch for new messages and process approval buttons.
|
|
279
|
+
*/
|
|
280
|
+
private _setupMessageProcessing() {
|
|
281
|
+
// Use a MutationObserver to watch for new messages and process approval buttons
|
|
282
|
+
const observer = new MutationObserver(mutations => {
|
|
283
|
+
mutations.forEach(mutation => {
|
|
284
|
+
mutation.addedNodes.forEach(node => {
|
|
285
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
286
|
+
const element = node as Element;
|
|
287
|
+
this._processApprovalButtons(element);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
observer.observe(this._chatPanel.node, {
|
|
294
|
+
childList: true,
|
|
295
|
+
subtree: true
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Processes text nodes to replace approval button placeholders with actual button elements.
|
|
301
|
+
*
|
|
302
|
+
* @param element - The element to search for approval button placeholders
|
|
303
|
+
*/
|
|
304
|
+
private _processApprovalButtons(element: Element) {
|
|
305
|
+
// Find all text nodes that contain approval buttons and replace them with actual buttons
|
|
306
|
+
const walker = document.createTreeWalker(
|
|
307
|
+
element,
|
|
308
|
+
NodeFilter.SHOW_TEXT,
|
|
309
|
+
null
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
const textNodes: Text[] = [];
|
|
313
|
+
let node;
|
|
314
|
+
while ((node = walker.nextNode())) {
|
|
315
|
+
textNodes.push(node as Text);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
textNodes.forEach(textNode => {
|
|
319
|
+
const text = textNode.textContent || '';
|
|
320
|
+
|
|
321
|
+
// Handle single tool approval buttons [APPROVAL_BUTTONS:id]
|
|
322
|
+
const singleMatch = text.match(/\[APPROVAL_BUTTONS:([^\]]+)\]/);
|
|
323
|
+
if (singleMatch) {
|
|
324
|
+
this._createSingleApprovalButtons(textNode, singleMatch[1]);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Handle grouped tool approval buttons [GROUP_APPROVAL_BUTTONS:groupId:id1,id2,id3]
|
|
329
|
+
const groupMatch = text.match(
|
|
330
|
+
/\[GROUP_APPROVAL_BUTTONS:([^:]+):([^\]]+)\]/
|
|
331
|
+
);
|
|
332
|
+
if (groupMatch) {
|
|
333
|
+
this._createGroupedApprovalButtons(
|
|
334
|
+
textNode,
|
|
335
|
+
groupMatch[1],
|
|
336
|
+
groupMatch[2]
|
|
337
|
+
);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Creates an approval button element with appropriate styling and classes.
|
|
345
|
+
*
|
|
346
|
+
* @param text - The button text
|
|
347
|
+
* @param isApprove - Whether this is an approve or reject button
|
|
348
|
+
* @param additionalClasses - Additional CSS classes to add
|
|
349
|
+
* @returns The created button element
|
|
350
|
+
*/
|
|
351
|
+
private _createApprovalButton(
|
|
352
|
+
text: string,
|
|
353
|
+
isApprove: boolean,
|
|
354
|
+
additionalClasses: string = ''
|
|
355
|
+
): HTMLButtonElement {
|
|
356
|
+
const button = document.createElement('button');
|
|
357
|
+
const baseClass = isApprove
|
|
358
|
+
? 'jp-ai-approval-approve'
|
|
359
|
+
: 'jp-ai-approval-reject';
|
|
360
|
+
button.className = `jp-ai-approval-btn ${baseClass}${additionalClasses ? ' ' + additionalClasses : ''}`;
|
|
361
|
+
button.textContent = text;
|
|
362
|
+
return button;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Creates and inserts approval buttons for a single tool call.
|
|
367
|
+
*
|
|
368
|
+
* @param textNode - The text node to replace with buttons
|
|
369
|
+
* @param interruptionId - The interruption ID for the tool call
|
|
370
|
+
*/
|
|
371
|
+
private _createSingleApprovalButtons(textNode: Text, interruptionId: string) {
|
|
372
|
+
// Create approval buttons for single tool
|
|
373
|
+
const buttonContainer = document.createElement('div');
|
|
374
|
+
buttonContainer.className = 'jp-ai-tool-approval-buttons';
|
|
375
|
+
buttonContainer.setAttribute('data-interruption-id', interruptionId);
|
|
376
|
+
|
|
377
|
+
// Try to find the message ID from the closest message container
|
|
378
|
+
const messageId = this._findMessageId(textNode);
|
|
379
|
+
if (messageId) {
|
|
380
|
+
buttonContainer.setAttribute('data-message-id', messageId);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const approveBtn = this._createApprovalButton('Approve', true);
|
|
384
|
+
const rejectBtn = this._createApprovalButton('Reject', false);
|
|
385
|
+
|
|
386
|
+
// Add click handlers directly to the buttons
|
|
387
|
+
this._addButtonHandler(approveBtn);
|
|
388
|
+
this._addButtonHandler(rejectBtn);
|
|
389
|
+
|
|
390
|
+
buttonContainer.appendChild(approveBtn);
|
|
391
|
+
buttonContainer.appendChild(rejectBtn);
|
|
392
|
+
|
|
393
|
+
// Replace the text node with the button container
|
|
394
|
+
const parent = textNode.parentNode;
|
|
395
|
+
if (parent) {
|
|
396
|
+
parent.replaceChild(buttonContainer, textNode);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Creates and inserts approval buttons for grouped tool calls.
|
|
402
|
+
*
|
|
403
|
+
* @param textNode - The text node to replace with buttons
|
|
404
|
+
* @param groupId - The group ID for the tool calls
|
|
405
|
+
* @param interruptionIds - Comma-separated interruption IDs
|
|
406
|
+
*/
|
|
407
|
+
private _createGroupedApprovalButtons(
|
|
408
|
+
textNode: Text,
|
|
409
|
+
groupId: string,
|
|
410
|
+
interruptionIds: string
|
|
411
|
+
) {
|
|
412
|
+
// Create approval buttons for grouped tools
|
|
413
|
+
const buttonContainer = document.createElement('div');
|
|
414
|
+
buttonContainer.className = 'jp-ai-group-approval-buttons';
|
|
415
|
+
buttonContainer.setAttribute('data-group-id', groupId);
|
|
416
|
+
buttonContainer.setAttribute('data-interruption-ids', interruptionIds);
|
|
417
|
+
|
|
418
|
+
// Try to find the message ID from the closest message container
|
|
419
|
+
const messageId = this._findMessageId(textNode);
|
|
420
|
+
if (messageId) {
|
|
421
|
+
buttonContainer.setAttribute('data-message-id', messageId);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const approveBtn = this._createApprovalButton(
|
|
425
|
+
'Approve',
|
|
426
|
+
true,
|
|
427
|
+
'jp-ai-group-approve-all'
|
|
428
|
+
);
|
|
429
|
+
const rejectBtn = this._createApprovalButton(
|
|
430
|
+
'Reject',
|
|
431
|
+
false,
|
|
432
|
+
'jp-ai-group-reject-all'
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
// Add click handlers for grouped approvals
|
|
436
|
+
this._addGroupedButtonHandler(approveBtn);
|
|
437
|
+
this._addGroupedButtonHandler(rejectBtn);
|
|
438
|
+
|
|
439
|
+
buttonContainer.appendChild(approveBtn);
|
|
440
|
+
buttonContainer.appendChild(rejectBtn);
|
|
441
|
+
|
|
442
|
+
// Replace the text node with the button container
|
|
443
|
+
const parent = textNode.parentNode;
|
|
444
|
+
if (parent) {
|
|
445
|
+
parent.replaceChild(buttonContainer, textNode);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Finds the message ID by traversing up the DOM tree from a text node.
|
|
451
|
+
*
|
|
452
|
+
* @param textNode - The text node to start searching from
|
|
453
|
+
* @returns The message ID if found, null otherwise
|
|
454
|
+
*/
|
|
455
|
+
private _findMessageId(textNode: Text): string | null {
|
|
456
|
+
let messageElement = textNode.parentNode;
|
|
457
|
+
while (messageElement && messageElement !== document.body) {
|
|
458
|
+
if (messageElement.nodeType === Node.ELEMENT_NODE) {
|
|
459
|
+
const element = messageElement as Element;
|
|
460
|
+
// Look for common message container attributes or classes
|
|
461
|
+
const messageId =
|
|
462
|
+
element.getAttribute('data-message-id') ||
|
|
463
|
+
element.getAttribute('id') ||
|
|
464
|
+
element
|
|
465
|
+
.querySelector('[data-message-id]')
|
|
466
|
+
?.getAttribute('data-message-id');
|
|
467
|
+
if (messageId) {
|
|
468
|
+
return messageId;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
messageElement = messageElement.parentNode;
|
|
472
|
+
}
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Fixes focus issue by replacing the global click handler with a more selective one.
|
|
478
|
+
* Only focuses the input when clicking empty areas, not on interactive elements.
|
|
479
|
+
*/
|
|
480
|
+
private _fixCopyFocusIssue() {
|
|
481
|
+
// Remove the global click handler that causes focus on any click
|
|
482
|
+
// The original handler is: this.node.onclick = () => this.model.input.focus();
|
|
483
|
+
if (this._chatPanel.node.onclick) {
|
|
484
|
+
this._chatPanel.node.onclick = null;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Add a more selective click handler that only focuses when clicking empty areas
|
|
488
|
+
this._chatPanel.node.addEventListener('click', (event: Event) => {
|
|
489
|
+
const target = event.target as HTMLElement;
|
|
490
|
+
|
|
491
|
+
// Don't focus if clicking on selectable text elements
|
|
492
|
+
const selection = window.getSelection();
|
|
493
|
+
if (
|
|
494
|
+
target.closest('pre') ||
|
|
495
|
+
target.closest('code') ||
|
|
496
|
+
target.closest('.jp-RenderedMarkdown') ||
|
|
497
|
+
target.closest('.jp-ai-tool-call') ||
|
|
498
|
+
target.closest('button') ||
|
|
499
|
+
target.closest('.jp-chat-input-container') ||
|
|
500
|
+
(selection && selection.toString().length > 0)
|
|
501
|
+
) {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Only focus input when clicking empty chat areas
|
|
506
|
+
this._chatModel.input.focus();
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Private fields
|
|
511
|
+
private _chatPanel: Widget;
|
|
512
|
+
private _chatModel: AIChatModel;
|
|
513
|
+
private _settingsModel: AISettingsModel;
|
|
514
|
+
private _toolbar: Toolbar;
|
|
515
|
+
private _commands: CommandRegistry;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Namespace for ChatWrapperWidget statics.
|
|
520
|
+
*/
|
|
521
|
+
export namespace ChatWrapperWidget {
|
|
522
|
+
/**
|
|
523
|
+
* The options for the constructor of the chat wrapper widget.
|
|
524
|
+
*/
|
|
525
|
+
export interface IOptions {
|
|
526
|
+
/**
|
|
527
|
+
* The chat panel widget to wrap.
|
|
528
|
+
*/
|
|
529
|
+
chatPanel: Widget;
|
|
530
|
+
/**
|
|
531
|
+
* The command registry for the chat wrapper.
|
|
532
|
+
*/
|
|
533
|
+
commands: CommandRegistry;
|
|
534
|
+
/**
|
|
535
|
+
* The chat model for the chat wrapper.
|
|
536
|
+
*/
|
|
537
|
+
chatModel: AIChatModel;
|
|
538
|
+
/**
|
|
539
|
+
* The settings model for the chat wrapper.
|
|
540
|
+
*/
|
|
541
|
+
settingsModel: AISettingsModel;
|
|
542
|
+
}
|
|
543
|
+
}
|